11# CLI E2E 测试规范
22
3+ ## 架构分层
4+
5+ | 层级 | 路径 | 测什么 |
6+ | --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
7+ | ** 共享基建** | ` packages/e2e ` | gating、子进程 runner、output、globalSetup(` private ` ,不发布) |
8+ | ** 命令 E2E** | ` packages/commands/tests/e2e ` | help、缺参、dry-run、live(gated);每用例最小路由 |
9+ | ** bl smoke** | ` packages/cli/tests/e2e/registry.smoke.e2e.test.ts ` | 产品 map 全部 path ` --help ` 、分组 help、根 help |
10+ | ** kscli smoke** | ` packages/kscli/tests/e2e/registry.smoke.e2e.test.ts ` | 从 ` kscli/src/commands.ts ` 推导 path/分组;identity(` --version ` 、` search --help ` path) |
11+ | ** runtime** | ` packages/runtime/tests ` | ` proxy.e2e ` 、console 跨域 flag 拒绝 |
12+
13+ ** 依赖边界** :` e2e ` → ` core ` ;` commands/tests ` → ` e2e ` + ` commands/src ` ;产品 tests → ` e2e ` + 各自 ` src ` 。** 禁止** 产品 import ` commands/tests/** ` (子进程 spawn harness 路径除外)。
14+
315## 触发条件
416
517- 新增/修改 ` packages/commands/src/commands ` 下的 command 实现
618- 新增/修改 ` packages/cli/src/commands.ts ` 的 ` bl ` 命令路径 map
7- - 新建或扩展 ` packages/cli/tests/e2e/*.e2e.test.ts ` 用例
8- - 为命令补 help / 缺参 / dry-run / 真实集成测试
19+ - 新建或扩展 ` packages/commands/tests/e2e/<topic>.e2e.test.ts `
20+ - 新增 bl 产品 path → ` registry.smoke ` 自动覆盖 leaf path;commands topic 测试在 ` topic-routes.ts ` 补最小路由
21+
22+ 跑测与环境变量见 ` .cursor/skills/bailian-cli-e2e/SKILL.md ` 。
923
10- 以上情况必须同步维护 ` packages/cli/ tests/e2e/<topic>.e2e.test.ts ` 。跑测与环境变量见 ` .cursor/skills/bailian-cli- e2e/SKILL.md ` 。
24+ > ** 规则 ** :共享 command 行为在 ` commands/ tests/e2e` ;产品 map、identity、CLI-only 命令留在对应产品 ` tests/ e2e` 。
1125
1226## 文件与工具
1327
14- - 路径:` packages/cli/tests/e2e/<kebab-topic>.e2e.test.ts `
15- - 框架:` vite-plus/test ` ;子进程跑 CLI:` runCli ` from ` ./helpers.ts `
28+ ### commands E2E
29+
30+ - 路径:` packages/commands/tests/e2e/<kebab-topic>.e2e.test.ts `
31+ - 子进程:` runCommandE2e(routes, args) ` from ` ./helpers.ts ` (spawn ` harness/main.ts ` ,` routes ` 为本 topic 最小 path → export 映射)
32+ - fixtures:` packages/commands/tests/e2e/fixtures/ `
33+ - 路由常量:` topic-routes.ts ` (按 topic 维护,** 非** 全量产品 map)
34+
35+ ### 产品 smoke
36+
37+ - bl:` runCli ` from ` packages/cli/tests/e2e/helpers.ts `
38+ - kscli:` runKscli ` from ` packages/kscli/tests/e2e/helpers.ts `
39+
40+ ### 共享
41+
42+ - gating / output / runner:` e2e/gating ` 、` e2e/output ` 、` e2e/runner `
43+ - globalSetup:根 ` vite.config.ts ` → ` packages/e2e/src/global-setup.ts `
1644- 解析 JSON stdout:` parseStdoutJson ` ;输出目录:` makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url)) `
1745- 长任务:` cliTimeoutPrefix() ` ;视频用例加 ` test(..., 3_600_000) ` 等显式超时
1846
1947## 双层 describe(固定结构)
2048
2149``` ts
22- // 1) 不 skip:分组 + --help,无密钥、无真实 API
50+ // 1) 不 skip:--help,无密钥、无真实 API(分组 help 由 bl registry.smoke 覆盖)
2351describe (" e2e: <topic>" , () => {
24- test (" <group> 分组展示子命令帮助且成功退出" , ... );
2552 test (" <subcommand> --help 正常退出" , ... );
2653});
2754
28- // 2) skipIf:缺参 / dry-run / 真实集成;原有集成用例放最后、勿改逻辑
55+ // 2) skipIf:缺参 / dry-run / 真实集成
2956describe .skipIf (<ready >)(" e2e: <topic>(DashScope …)" , () => {
3057 test (" 缺少 --<flag> 时退出为用法错误 (2)" , ... );
3158 test (" <cmd> --dry-run ..." , ... ); // 若适用
3259 test (" 【model】真实流程" , ... , LONG_TIMEOUT );
3360});
3461```
3562
36- ## skip 条件(helpers.ts )
63+ ## skip 条件(` e2e/gating ` ,commands helpers re-export )
3764
38- | 场景 | 条件 |
39- | ------------------- | ----------------------------------------------------- |
40- | 文本/搜索/记忆/配置 | ` isDashScopeE2EReady() ` |
41- | 图像/语音 | ` isBailianE2EMediaEnabled() && isDashScopeE2EReady() ` |
42- | 视频 | ` isBailianE2EVideoEnabled() && isDashScopeE2EReady() ` |
43- | 知识库 | ` isKnowledgeE2EReady() ` |
44- | 视频 download/task | 另需 ` BAILIAN_E2E_VIDEO_TASK_ID ` |
65+ | 场景 | 条件 |
66+ | ----------------------- | ----------------------------------------------------- ----------------------------------------------------- |
67+ | 文本/搜索/记忆/配置 | ` isDashScopeE2EReady() ` |
68+ | 图像/语音 | ` isBailianE2EMediaEnabled() && isDashScopeE2EReady() ` |
69+ | 视频 | ` isBailianE2EVideoEnabled() && isDashScopeE2EReady() ` |
70+ | 视频 download/task | 另需 ` BAILIAN_E2E_VIDEO_TASK_ID ` |
71+ | 知识库 chat/search live | ` isChatE2EReady() ` / ` isSearchE2EReady() ` ( ` knowledge chat/search ` ,需 ` BAILIAN_WORKSPACE_ID ` + agent ID) |
4572
4673## 用例类型
4774
48- 1 . ** 分组 help** :` runCli(["image"]) ` → ` exitCode === 0 ` ,stdout+stderr 含子命令名
49- 2 . ** --help** :` runCli([..., "--help"]) ` → stderr 含主要 flags
50- 3 . ** 缺参** :带一个无害全局 flag(如 ` --quiet ` )且不传 required flag → ` exitCode === 2 ` ,stderr 匹配 ` --flag|Missing required argument `
51- 4 . ** --dry-run** :仅当实现在联网/上传/写盘** 之前** 返回;断言 stdout JSON/文本,不入网
52- 5 . ** 真实集成** :保留既有用例名称与断言;放在 skip 块** 末尾**
75+ 1 . ** --help** :` runCommandE2e(ROUTES, [..., "--help"]) ` → stderr 含主要 flags
76+ 2 . ** 缺参** :带无害全局 flag(如 ` --quiet ` )且不传 required flag → ` exitCode === 2 `
77+ 3 . ** --dry-run** :实现在联网/上传/写盘** 之前** 返回;断言 stdout JSON/文本
78+ 4 . ** 真实集成** :放在 skip 块** 末尾**
79+
80+ ## 增删命令同步
81+
82+ - ** commands export** + ** topic 路由** (` topic-routes.ts ` 或测试文件内 ` ROUTES ` )+ ** 产品 map** (` cli/commands.ts ` / ` kscli/commands.ts ` )
83+ - 分组 help 由产品 ` registry.smoke ` 负责,无需在 commands 重复
5384
5485## 安全与例外
5586
@@ -60,24 +91,36 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
6091
6192## 新增 command 检查清单
6293
63- - [ ] ` packages/commands/src/index.ts ` 导出 + ` packages/cli/src/commands.ts ` 暴露路径 + ` tests/e2e/<topic>.e2e.test.ts ` (新建或扩展)
94+ - [ ] ` packages/commands/src/index.ts ` 导出 + ` packages/cli/src/commands.ts ` 暴露路径 + ` topic-routes.ts ` 补最小路由
95+ - [ ] ` packages/commands/tests/e2e/<topic>.e2e.test.ts ` (新建或扩展)
6496- [ ] 若改了 ` usageArgs ` / ` flags ` / ` exampleArgs ` ,跑 ` pnpm --filter bailian-cli run generate:reference ` 更新 ` skills/bailian-cli/reference/ ` 并提交
65- - [ ] 顶层:分组 help + 子命令 ` --help ` (多子命令则各一条 help)
97+ - [ ] 子命令 ` --help ` (分组 help 由 bl ` registry.smoke ` 覆盖 )
6698- [ ] skip 块:每个 required flag 缺参;可 dry-run 则加一条
6799- [ ] 至少一条真实集成(或说明为何仅 smoke);不破坏已有集成用例顺序
68- - [ ] ` pnpm test packages/cli/tests/e2e/<file> ` 通过
100+ - [ ] ` vp test packages/commands/tests/e2e/<file> ` 通过
101+
102+ ## 调试命令
103+
104+ ``` sh
105+ pnpm --filter bailian-cli-commands exec vp test packages/commands/tests/e2e/text-chat.e2e.test.ts
106+ pnpm --filter bailian-cli exec vp test packages/cli/tests/e2e/registry.smoke.e2e.test.ts
107+ pnpm --filter knowledge-studio-cli exec vp test packages/kscli/tests/e2e/registry.smoke.e2e.test.ts
108+ pnpm --filter bailian-cli-runtime exec vp test packages/runtime/tests/proxy.e2e.test.ts
109+ ```
69110
70111## 示例片段
71112
72113``` ts
114+ import { FOO_ROUTES } from " ./topic-routes.ts" ;
115+
73116test (" foo bar 缺少 --prompt 时退出为用法错误 (2)" , async () => {
74- const { stderr, exitCode } = await runCli ( [" foo" , " bar" , " --quiet" ]);
117+ const { stderr, exitCode } = await runCommandE2e ( FOO_ROUTES , [" foo" , " bar" , " --quiet" ]);
75118 expect (exitCode ).toBe (2 );
76119 expect (stderr ).toMatch (/ --prompt| Missing required argument/ i );
77120});
78121
79122test (" foo bar --dry-run 仅输出计划" , async () => {
80- const { stdout, stderr, exitCode } = await runCli ( [
123+ const { stdout, stderr, exitCode } = await runCommandE2e ( FOO_ROUTES , [
81124 " foo" ,
82125 " bar" ,
83126 " --dry-run" ,
@@ -97,4 +140,4 @@ test("foo bar --dry-run 仅输出计划", async () => {
97140- ** E2E** :单条/少量调用、断言固定、可进 ` vp test ` (见上文 skip 条件)
98141- ** 批量压测** :` packages/cli/tests/stress/run.mjs ` + ` targets/*.mjs ` ,并发 + 报告,** 仅手动** ` pnpm run test:stress -- <target> `
99142
100- 勿把压测并入 E2E 或默认 CI。详见 [ stress-batch-tests.md] ( stress-batch-tests.md ) 。
143+ 勿把压测并入 E2E 或默认 CI。详见 [ stress-batch-tests.md] ( stress-batch-tests.md ) .
0 commit comments