## ADDED Requirements ### Requirement: E2E 测试基础设施 前端 E2E 测试 SHALL 自动启动隔离的 Go 后端实例,提供真实 API 交互能力。 #### Scenario: 双 webServer 自动启动 - **WHEN** 执行 `bun run test:e2e` - **THEN** Playwright SHALL 按 webServer 数组顺序先启动 Go 后端(端口 19026),再启动 Vite 前端(端口 5173) - **THEN** Go 后端 SHALL 使用临时目录中的独立数据库文件和日志目录 - **THEN** Go 后端启动命令 SHALL 包含 `--server-port 19026`、`--database-path`、`--log-path`、`--log-level warn` 参数 - **THEN** Go 后端 cwd SHALL 指向 `backend/` 目录 - **THEN** webServer 等待超时 SHALL 为 60000ms - **THEN** 两个 webServer SHALL 设置 `reuseExistingServer: false` #### Scenario: 环境变量传递 - **WHEN** Playwright 配置加载 - **THEN** `playwright.config.ts` SHALL 设置 `process.env.NEX_BACKEND_PORT` 为 `'19026'` - **THEN** `process.env.NEX_E2E_TEMP_DIR` SHALL 设置为临时目录路径 - **THEN** Vite dev server 子进程 SHALL 自动继承上述环境变量 - **THEN** E2E 测试文件 SHALL 通过 `process.env` 读取后端端口和临时目录信息 #### Scenario: Vite proxy 动态化 - **WHEN** `vite.config.ts` 配置 proxy target - **THEN** proxy target SHALL 读取 `process.env.NEX_BACKEND_PORT`,回退到 `'9826'` - **THEN** 日常 `bun run dev` 时(无环境变量)proxy target SHALL 为 `http://localhost:9826` - **THEN** E2E 测试时 proxy target SHALL 为 `http://localhost:19026` ### Requirement: 临时文件隔离 E2E 测试 SHALL 使用固定临时目录隔离所有文件,确保主进程与 worker 进程使用同一路径,测试结束后自动清理。 #### Scenario: 临时目录创建 - **WHEN** Playwright 配置加载 - **THEN** SHALL 使用固定路径 `path.join(os.tmpdir(), 'nex-e2e')` 作为临时目录 - **THEN** SHALL 先执行 `fs.rmSync(tempDir, { recursive: true, force: true })` 清理残留 - **THEN** SHALL 执行 `fs.mkdirSync` 创建 `log/` 子目录 - **THEN** 临时目录 SHALL 包含 `test.db`(数据库)和 `log/`(日志)子路径 #### Scenario: 临时目录清理 - **WHEN** Playwright 所有测试完成 - **THEN** `globalTeardown` SHALL 使用固定路径 `path.join(os.tmpdir(), 'nex-e2e')` 定位临时目录 - **THEN** SHALL 使用 `fs.rmSync(dir, { recursive: true, force: true })` 删除临时目录 - **THEN** 清理 SHALL 在 webServer 进程关闭之后执行 ### Requirement: 统计数据 seed E2E 测试 SHALL 能通过 sql.js 直接操作 SQLite 文件 seed 历史统计数据。 #### Scenario: SQLite 统计数据插入 - **WHEN** 统计页面测试需要历史数据 - **THEN** SHALL 使用 sql.js 打开临时数据库文件 - **THEN** SHALL 通过 `INSERT INTO usage_stats` 插入指定日期和请求量的统计数据 - **THEN** SHALL 将修改后的数据库写回文件(`db.export()` → `fs.writeFileSync`) - **THEN** seed 操作 SHALL 在 API 创建供应商和模型之后串行执行 - **THEN** seed 完成后 SHALL 关闭 sql.js 数据库连接 ### Requirement: E2E 共享工具模块 E2E 测试 SHALL 提供共享工具模块(`e2e/fixtures.ts`)封装常用操作。 #### Scenario: 共享常量 - **WHEN** 测试文件需要后端 API 地址 - **THEN** fixtures SHALL export `API_BASE` 常量,值为 `http://localhost:${process.env.NEX_BACKEND_PORT}` #### Scenario: API seed 工具 - **WHEN** 测试需要通过 API 准备数据 - **THEN** fixtures SHALL export `seedProvider(request, data)` 函数,通过 `request.post` 创建供应商 - **THEN** fixtures SHALL export `seedModel(request, data)` 函数,通过 `request.post` 创建模型 #### Scenario: SQLite seed 工具 - **WHEN** 测试需要 seed 统计数据 - **THEN** fixtures SHALL export `seedUsageStats(statsData)` 函数,通过 sql.js 插入 `usage_stats` 记录 ### Requirement: 供应商管理 E2E 测试 E2E 测试 SHALL 验证供应商的完整 CRUD 用户流程。 #### Scenario: 创建供应商并验证 - **WHEN** 用户通过 UI 填写供应商表单并提交 - **THEN** 对话框 SHALL 关闭 - **THEN** 新供应商 SHALL 出现在表格中 - **THEN** 表格 SHALL 显示正确的 id、name、base_url、协议、enabled 状态 #### Scenario: 编辑供应商并验证 - **WHEN** 用户点击编辑按钮、修改名称并提交 - **THEN** 对话框 SHALL 关闭 - **THEN** 表格中该供应商的名称 SHALL 更新为新值 #### Scenario: 删除供应商并验证 - **WHEN** 用户点击删除按钮并确认 - **THEN** 确认框 SHALL 消失 - **THEN** 该供应商 SHALL 从表格中消失 ### Requirement: 模型管理 E2E 测试 E2E 测试 SHALL 验证模型的完整 CRUD 用户流程,断言 SHALL 精确匹配以避免多列文本干扰。 #### Scenario: 前置数据准备 - **WHEN** 模型测试开始前 - **THEN** SHALL 通过 API seed 一个供应商(不通过 UI) #### Scenario: 创建模型并验证 - **WHEN** 用户展开供应商行、填写模型表单并提交 - **THEN** 对话框 SHALL 关闭 - **THEN** 新模型 SHALL 出现在展开行的模型表格中 - **THEN** 模型表格 SHALL 显示统一模型 ID(`provider_id/model_name` 格式) - **THEN** 验证断言 SHALL 使用 `{ exact: true }` 精确匹配模型名称文本,避免匹配到"统一模型 ID"列 #### Scenario: 编辑模型并验证 - **WHEN** 用户点击模型编辑按钮、修改并提交 - **THEN** 对话框 SHALL 关闭 - **THEN** 模型表格 SHALL 显示更新后的数据 - **THEN** 验证断言 SHALL 使用 `{ exact: true }` 精确匹配模型名称文本 #### Scenario: 删除模型并验证 - **WHEN** 用户点击模型删除按钮并确认 - **THEN** 该模型 SHALL 从模型表格中消失 - **THEN** 验证断言 SHALL 使用 `{ exact: true }` 精确匹配模型名称文本 ### Requirement: 统计页面 E2E 测试 E2E 测试 SHALL 验证统计页面的数据展示和筛选功能。所有测试用例 SHALL NOT 被跳过。 #### Scenario: 统计数据准备 - **WHEN** 统计测试开始前 - **THEN** SHALL 通过 API seed 供应商和模型 - **THEN** SHALL 通过 sql.js seed 多日历史统计数据(覆盖不同供应商、不同模型、不同日期) #### Scenario: 统计概览验证 - **WHEN** 加载统计页面 - **THEN** 统计摘要卡片 SHALL 显示正确的总请求量 - **THEN** 统计摘要卡片 SHALL 显示正确的活跃模型数和活跃供应商数 - **THEN** 统计表格 SHALL 显示 seed 的数据行 - **THEN** 页面 SHALL 渲染趋势图表区域(标题包含"请求趋势") #### Scenario: 统计筛选验证 - **WHEN** 页面加载完成 - **THEN** 页面 SHALL 显示筛选栏(供应商下拉选择和模型名称输入框) - **WHEN** 用户选择供应商筛选条件 - **THEN** 统计表格 SHALL 只显示该供应商的数据 - **WHEN** 用户输入模型名称筛选条件 - **THEN** 统计表格 SHALL 只显示匹配模型的数据 ### Requirement: 导航 E2E 测试 E2E 测试 SHALL 验证页面导航和侧边栏功能。 #### Scenario: 侧边栏渲染 - **WHEN** 加载任意页面 - **THEN** 侧边栏 SHALL 显示应用名称和导航菜单项 #### Scenario: 页面切换 - **WHEN** 用户点击侧边栏导航菜单项 - **THEN** 页面 SHALL 切换到对应路由并显示正确内容 - **THEN** 当前菜单项 SHALL 高亮 #### Scenario: URL 持久化 - **WHEN** 用户在统计页面刷新浏览器 - **THEN** 页面 SHALL 保持在统计页面 ### Requirement: 表单验证 E2E 测试 E2E 测试 SHALL 验证表单的校验行为。 #### Scenario: 供应商表单必填校验 - **WHEN** 用户打开添加供应商对话框并直接点击保存 - **THEN** SHALL 显示所有必填字段的验证错误提示 #### Scenario: URL 格式校验 - **WHEN** 用户在 base_url 字段输入无效 URL 并提交 - **THEN** SHALL 显示 URL 格式错误提示 #### Scenario: 对话框行为 - **WHEN** 用户打开对话框后点击取消 - **THEN** 对话框 SHALL 关闭且表单重置 - **WHEN** 用户再次打开对话框 - **THEN** 表单字段 SHALL 为空 ### Requirement: E2E 测试文件组织 E2E 测试 SHALL 按 7 个 spec 文件组织。 #### Scenario: 文件结构 - **WHEN** 查看 `e2e/` 目录 - **THEN** SHALL 包含以下测试文件:`providers.spec.ts`、`models.spec.ts`、`stats.spec.ts`、`navigation.spec.ts`、`validation.spec.ts` - **THEN** SHALL 包含以下辅助文件:`fixtures.ts`、`global-setup.ts`、`global-teardown.ts` - **THEN** SHALL NOT 包含已删除的文件:`stats-cards.spec.ts`、`sidebar.spec.ts`、`crud.spec.ts` ### Requirement: 新增依赖 E2E 测试 SHALL 引入 sql.js 作为 devDependency。 #### Scenario: sql.js 依赖 - **WHEN** 安装前端依赖 - **THEN** `package.json` 的 devDependencies SHALL 包含 `sql.js` - **THEN** `package.json` 的 devDependencies SHALL 包含 `@types/sql.js` - **THEN** sql.js SHALL 仅在 E2E 测试的 seed 逻辑中使用,不影响生产代码