This commit is contained in:
2026-09-12 14:19:56 +08:00
commit 87ab0b794b
130 changed files with 30042 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { test, expect, _electron as electron, type ElectronApplication, type Page } from '@playwright/test'
import { join } from 'path'
/**
* 守护 Monaco 懒加载的「运行时」正确性。
*
* v1 这里还有「ELK Worker 在 worker 里完成布局」的用例CallGraph 组件用 ELK 跑
* 布局chore/comprehensive-optimization 把 CallGraph 整个砍掉之后,那条用例
* 失去意义,留着只会锁住"CallGraph 必须存在"的反向约束,移走。
*
* 为什么必须有 e2e 兜lazy 边界一旦加载失败Suspense 只会一直显示 fallback 骨架,
* 页面看起来「在加载」—— typecheck + build 全绿但运行时静默坏掉。修这种 bug 不会
* 让 ESLint / tsc 报警,必须在真窗口里断言。
*/
let app: ElectronApplication
let page: Page
const pageErrors: string[] = []
test.beforeAll(async () => {
// 透传父进程 env但必须剔掉 ELECTRON_RUN_AS_NODEPlaywright 自己是 Electron-as-Node
// 跑的,继承这个变量会让被测应用也退化成纯 Node永远开不出窗口。
const env: Record<string, string> = {}
for (const [k, v] of Object.entries(process.env)) {
if (k !== 'ELECTRON_RUN_AS_NODE' && v !== undefined) env[k] = v
}
app = await electron.launch({ args: [join(__dirname, '..', 'out', 'main', 'index.js')], env })
page = await app.firstWindow()
page.on('pageerror', (e) => pageErrors.push(e.message))
// 启动后等待首屏标题
await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible()
await page.reload()
})
test.afterAll(async () => {
await app?.close()
})
test('Monaco 懒加载:骨架屏被真实编辑器替换,且编辑器可编辑', async () => {
// 不 waitForTimeout —— 用 web-first 断言等 lazy chunk 落地
const editor = page.locator('.monaco-editor')
await expect(editor).toBeVisible({ timeout: 30_000 })
// 骨架屏必须让位。它带 role="status" + aria-label="app.editorLoading"App.tsx 用
// useT 拿当前 lang 翻译;中英都覆盖)。默认 lang 是 zh-CN,所以这里断言中文文案。
await expect(page.getByLabel('编辑器加载中…')).toHaveCount(0)
// 编辑器不只是「渲染了个壳」:示例代码必须真的被 Monaco tokenize 出来。
// sample.ts 当前是 `def matinv(A):` —— 跟着示例从「矩阵乘法」改成「线性回归」一并换掉。
await expect(page.locator('.view-line').first()).toBeVisible()
await expect(page.locator('.monaco-editor')).toContainText('matinv')
})
test('全程没有未捕获的渲染进程异常', async () => {
expect(pageErrors).toEqual([])
})

56
e2e/smoke.spec.ts Normal file
View File

@@ -0,0 +1,56 @@
import { test, expect, _electron as electron } from '@playwright/test'
import { join } from 'path'
/**
* 端到端冒烟:覆盖 v2 的 UI 流程(不依赖已删除的 CallGraph / LineHeatmap /
* ExportButton / FindingsList
*
* v1 残留断言("Hotspots" / "build_report" / "What to fix" / "Lines" tab /
* "导出 JSON")随着 v2 重构(见 chore/comprehensive-optimization一并移除。
* 跑这条用例需要的本地条件:检测到至少一个 Python 解释器CI matrix 上预装)。
*/
test('端到端:加载示例 → 运行分析 → 摘要 + 热点表 + 火焰图', async () => {
// Electron 在 ELECTRON_RUN_AS_NODE 存在时会退化为纯 Node需从环境中移除
const env: Record<string, string> = {}
for (const [k, v] of Object.entries(process.env)) {
if (k !== 'ELECTRON_RUN_AS_NODE' && v !== undefined) env[k] = v
}
const app = await electron.launch({
args: [join(__dirname, '..', 'out', 'main', 'index.js')],
env
})
const page = await app.firstWindow()
// 标题存在TopBar 渲染 h1 + 副标lang 切换后 h1 文案跟着切)
await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible()
await page.reload()
await expect(page.getByRole('heading', { name: 'Python 程序耗时可视化分析' })).toBeVisible()
// 等待解释器检测填充(并行跑 5 个 phase最坏情况要 15-20s
const runBtn = page.getByRole('button', { name: '运行分析' })
try {
await expect(runBtn).toBeEnabled({ timeout: 30_000 })
} catch {
await app.close()
test.skip(true, '本机未检测到 Python 解释器')
return
}
await runBtn.click()
// 摘要RunSummaryLite出现 = 分析成功,引擎至少跑完了一次 wall-time 测量
await expect(page.getByText('本次耗时')).toBeVisible({ timeout: 60_000 })
// 热点表 sectionv2 标题 = results.hotspot.title
await expect(page.getByText('热点函数')).toBeVisible()
// DEMO_CODE 里的对比函数:纯 Python 高斯消元 vs numpy —— 最耗时的 `matinv` 一定出现在表里
await expect(page.getByText('matinv').first()).toBeVisible()
// 火焰图 sectionv2 标题 = results.time.title = "耗时分布"),确保 SVG 真的渲染
await expect(page.getByText('耗时分布')).toBeVisible()
await expect(page.locator('svg[aria-label="火焰图"]')).toBeVisible()
await app.close()
})