1
0
Files
nex/openspec/changes/e2e-real-backend/design.md
lanyuanxiaoyao 59179094ed feat: E2E 测试集成真实后端
- Playwright 双 webServer 模式自动启动 Go 后端 + Vite 前端
- 后端使用临时 SQLite 数据库隔离,固定端口 19026
- vite.config.ts proxy target 动态读取环境变量
- 新增 sql.js 依赖用于 SQLite 统计数据 seed
- 新增 e2e/fixtures.ts 共享工具模块(API seed + SQLite seed)
- 拆分测试文件 5→7(providers/models/stats/navigation/validation)
- 删除旧文件 crud.spec.ts/sidebar.spec.ts/stats-cards.spec.ts
- E2E 测试尚有部分用例需调试修复
2026-04-22 00:31:35 +08:00

86 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
前端 Playwright E2E 测试当前只启动 `bun run dev`Vite dev server后端不参与。现有 5 个 E2E 测试文件中,所有写操作(创建/编辑/删除供应商和模型)的验证都不完整——因为 API 请求无法到达后端,表单提交后的数据持久化无法验证,大量测试在缺少已有数据时直接 `test.skip()`
后端具备天然测试友好特性Go CLI 参数可覆盖所有配置(`--server-port``--database-path``--log-path``--log-level`SQLite 文件数据库使每次测试可用独立临时文件,首次启动自动运行 goose 迁移,有 `/health` 端点可供就绪检查。
## Goals / Non-Goals
**Goals:**
- `bun run test:e2e` 一条命令自动启动隔离后端 + 前端,运行完整 E2E 测试,自动清理
- 每次测试运行使用干净的临时数据库,测试间无状态污染
- E2E 测试能验证完整 CRUD 流程:创建→验证存在→编辑→验证更新→删除→验证消失
- 统计页面可通过 seed 数据验证数字、筛选、图表渲染
- 对日常开发流程零侵入(`bun run dev` 行为不变)
- Windows 原生兼容(项目在 Windows 上开发)
**Non-Goals:**
- 不修改后端代码(仅通过 CLI 参数启动现有二进制)
- 不引入 CI 流水线配置
- 不改动 MSW 在单元测试中的使用
- 不实现随机端口分配(使用固定非默认端口 19026
- 不引入 cross-env 等跨平台环境变量工具
## Decisions
### Decision 1: 固定测试端口 19026 vs 随机端口
**选择**: 固定端口 19026
**理由**: 随机端口需要在 `globalSetup` 中 spawn 后端进程、等待健康检查、管理进程生命周期(包括 Windows 上的 SIGTERM 问题复杂度高。19026 是非标准端口,与开发端口 9826 不冲突冲突概率极低。Playwright `webServer` 数组模式可直接管理两个进程的生命周期,无需手动 spawn/kill。
**备选方案**: `globalSetup` 中分配随机端口 → spawn 后端 → 等待健康检查 → `globalTeardown` 中 kill 进程。在 Windows 上 SIGTERM 不可靠,需 taskkill 强杀,进程管理复杂。
### Decision 2: 环境变量传递方式
**选择**: `process.env` 继承,不引入 cross-env
**理由**: Playwright 的 `webServer` 通过 `child_process.spawn` 启动子进程,子进程默认继承父进程 `process.env`。在 `playwright.config.ts` 模块顶层设置 `process.env.NEX_BACKEND_PORT = '19026'`Vite dev server 子进程自动继承,`vite.config.ts``defineConfig` 函数可读取。E2E 测试文件与 config 同进程,直接读取即可。三处消费者(后端 CLI flags / Vite / 测试文件)都无需额外工具。
### Decision 3: 统计数据 seed 方式
**选择**: sql.js 直接操作 SQLite 文件
**理由**: 统计数据(`usage_stats` 表)只能通过后端 `statsService.Record()` 按当天日期 upsert没有 API 可以插入任意日期的历史数据。统计页面测试需要多日趋势数据来验证筛选和图表。sql.js 是纯 WASM 实现无原生编译依赖Windows 上零风险。通过"读文件→内存操作→写回"模式在 `beforeAll` 中 seed 数据,串行化保证无并发冲突。
**备选方案**:
- better-sqlite3需要原生编译Windows 上可能有编译工具链问题
- 后端加测试专用 seed API需要修改后端代码违反"不改后端"原则
- 只测今日数据:无法验证日期筛选和趋势图表,测试覆盖不足
### Decision 4: 临时文件管理
**选择**: `fs.mkdtempSync(os.tmpdir() + '/nex-e2e-')` 创建,`fs.rm(dir, { recursive: true, force: true })``globalTeardown` 清理
**理由**: `mkdtempSync` 跨平台安全Windows 上生成 `%TEMP%\nex-e2e-xxxxxxxx` 路径。Go 后端的 `--database-path``--log-path` 参数接受 Windows 路径。`fs.rm` 是 Node.js 14.14+ 内置的递归删除 API无需额外依赖rimraf 等)。
### Decision 5: 测试文件拆分 5→7
**选择**: 按职责拆分
| 新文件 | 来源 | 职责 |
|--------|------|------|
| `providers.spec.ts` | 重写 `providers.spec.ts` + `crud.spec.ts` 供应商部分 | 供应商完整 CRUD 验证 |
| `models.spec.ts` | 新增,来自 `crud.spec.ts` 模型部分 | 模型管理完整验证 |
| `stats.spec.ts` | 重写 `stats.spec.ts` + 合并 `stats-cards.spec.ts` | 统计页面完整验证 |
| `navigation.spec.ts` | 新增,来自 `sidebar.spec.ts` + 导航测试 | 导航和侧边栏 |
| `validation.spec.ts` | 新增,来自 `crud.spec.ts` 表单验证部分 | 表单校验测试 |
| `fixtures.ts` | 新增 | 共享常量和 seed 工具函数 |
| `global-setup.ts` / `global-teardown.ts` | 新增 | 临时目录生命周期 |
### Decision 6: 进程启动顺序
**选择**: Playwright `webServer` 数组,后端在前、前端在后
**理由**: Playwright 按数组顺序依次等待每个 webServer 的 `url` 就绪。后端先启动(`/health` 返回 200再启动前端Vite 默认端口 5173`timeout: 60000` 覆盖 Go 首次编译耗时。两个 webServer 均设 `reuseExistingServer: false`,确保每次运行都是全新进程。
## Risks / Trade-offs
- **[Go 编译缓存]** → 首次 `go run` 需要 5-10s 编译,后续利用 Go 构建缓存加速。`timeout: 60000` 提供充足缓冲。如仍嫌慢,可手动 `cd backend && go build ./cmd/server/` 预编译。
- **[端口 19026 冲突]** → 概率极低。Playwright 报明确错误,用户手动释放即可。不考虑自动重试。
- **[Ctrl+C 强退临时文件残留]** → `globalTeardown` 不执行,但下次运行创建新目录,旧目录不占端口。可接受。
- **[SQLite WAL 文件]** → 后端进程被 Playwright 强杀时可能留下 `.db-wal`/`.db-shm` 文件,但随临时目录一起删除,无影响。
- **[sql.js 串行化约束]** → seed 数据时需确保 API 操作完成后再操作 SQLite。在 `beforeAll` 中串行执行,不存在并发问题。