| 层级 | 路径 | 测什么 |
|---|---|---|
| 共享基建 | packages/e2e |
gating、子进程 runner、output、globalSetup(private,不发布) |
| 命令 E2E | packages/commands/tests/e2e |
help、缺参、dry-run、live(gated);每用例最小路由 |
| bl smoke | packages/cli/tests/e2e/registry.smoke.e2e.test.ts |
产品 map 全部 path --help、分组 help、根 help |
| kscli smoke | packages/kscli/tests/e2e/registry.smoke.e2e.test.ts |
从 kscli/src/commands.ts 推导 path/分组;identity(--version、search --help path) |
| runtime | packages/runtime/tests |
proxy.e2e、console 跨域 flag 拒绝 |
依赖边界:e2e → core;commands/tests → e2e + commands/src;产品 tests → e2e + 各自 src。禁止产品 import commands/tests/**(子进程 spawn harness 路径除外)。
- 新增/修改
packages/commands/src/commands下的 command 实现 - 新增/修改
packages/cli/src/commands.ts的bl命令路径 map - 新建或扩展
packages/commands/tests/e2e/<topic>.e2e.test.ts - 新增 bl 产品 path →
registry.smoke自动覆盖 leaf path;commands topic 测试在topic-routes.ts补最小路由
跑测与环境变量见 .cursor/skills/bailian-cli-e2e/SKILL.md。
规则:共享 command 行为在
commands/tests/e2e;产品 map、identity、CLI-only 命令留在对应产品tests/e2e。
- 路径:
packages/commands/tests/e2e/<kebab-topic>.e2e.test.ts - 子进程:
runCommandE2e(routes, args)from./helpers.ts(spawnharness/main.ts,routes为本 topic 最小 path → export 映射) - fixtures:
packages/commands/tests/e2e/fixtures/ - 路由常量:
topic-routes.ts(按 topic 维护,非全量产品 map)
- bl:
runClifrompackages/cli/tests/e2e/helpers.ts - kscli:
runKsclifrompackages/kscli/tests/e2e/helpers.ts
- gating / output / runner:
e2e/gating、e2e/output、e2e/runner - globalSetup:根
vite.config.ts→packages/e2e/src/global-setup.ts - 解析 JSON stdout:
parseStdoutJson;输出目录:makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url)) - 长任务:
cliTimeoutPrefix();视频用例加test(..., 3_600_000)等显式超时
// 1) 不 skip:--help,无密钥、无真实 API(分组 help 由 bl registry.smoke 覆盖)
describe("e2e: <topic>", () => {
test("<subcommand> --help 正常退出", ...);
});
// 2) skipIf:缺参 / dry-run / 真实集成
describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
test("缺少 --<flag> 时退出为用法错误 (2)", ...);
test("<cmd> --dry-run ...", ...); // 若适用
test("【model】真实流程", ..., LONG_TIMEOUT);
});| 场景 | 条件 |
|---|---|
| 文本/搜索/记忆/配置 | isDashScopeE2EReady() |
| 图像/语音 | isBailianE2EMediaEnabled() && isDashScopeE2EReady() |
| 视频 | isBailianE2EVideoEnabled() && isDashScopeE2EReady() |
| 视频 download/task | 另需 BAILIAN_E2E_VIDEO_TASK_ID |
| 知识库 chat/search live | isChatE2EReady() / isSearchE2EReady()(knowledge chat/search,需 BAILIAN_WORKSPACE_ID + agent ID) |
- --help:
runCommandE2e(ROUTES, [..., "--help"])→ stderr 含主要 flags - 缺参:带无害全局 flag(如
--quiet)且不传 required flag →exitCode === 2 - --dry-run:实现在联网/上传/写盘之前返回;断言 stdout JSON/文本
- 真实集成:放在 skip 块末尾
- commands export + topic 路由(
topic-routes.ts或测试文件内ROUTES)+ 产品 map(cli/commands.ts/kscli/commands.ts) - 分组 help 由产品
registry.smoke负责,无需在 commands 重复
- 禁止真实破坏性操作:
auth logout只用--dry-run;config set只用--dry-run - 不加 dry-run:
dryRun在resolveFileUrl/resolveCredential/ 上传之后的命令(如image edit、speech recognize带--url) --list-voices等旁路:先于--text校验的 flag,缺参用例勿带该 flag- 新增 required option → 至少一条缺参用例;改 dry-run 输出 → 更新对应断言
-
packages/commands/src/index.ts导出 +packages/cli/src/commands.ts暴露路径 +topic-routes.ts补最小路由 -
packages/commands/tests/e2e/<topic>.e2e.test.ts(新建或扩展) - 若改了
usageArgs/flags/exampleArgs,跑pnpm --filter bailian-cli run generate:reference更新skills/bailian-cli/reference/并提交 - 子命令
--help(分组 help 由 blregistry.smoke覆盖) - skip 块:每个 required flag 缺参;可 dry-run 则加一条
- 至少一条真实集成(或说明为何仅 smoke);不破坏已有集成用例顺序
-
vp test packages/commands/tests/e2e/<file>通过
pnpm --filter bailian-cli-commands exec vp test packages/commands/tests/e2e/text-chat.e2e.test.ts
pnpm --filter bailian-cli exec vp test packages/cli/tests/e2e/registry.smoke.e2e.test.ts
pnpm --filter knowledge-studio-cli exec vp test packages/kscli/tests/e2e/registry.smoke.e2e.test.ts
pnpm --filter bailian-cli-runtime exec vp test packages/runtime/tests/proxy.e2e.test.tsimport { FOO_ROUTES } from "./topic-routes.ts";
test("foo bar 缺少 --prompt 时退出为用法错误 (2)", async () => {
const { stderr, exitCode } = await runCommandE2e(FOO_ROUTES, ["foo", "bar", "--quiet"]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/--prompt|Missing required argument/i);
});
test("foo bar --dry-run 仅输出计划", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(FOO_ROUTES, [
"foo",
"bar",
"--dry-run",
"--prompt",
"x",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ request?: unknown }>(stdout);
expect(data.request).toBeDefined();
});- E2E:单条/少量调用、断言固定、可进
vp test(见上文 skip 条件) - 批量压测:
packages/cli/tests/stress/run.mjs+targets/*.mjs,并发 + 报告,仅手动pnpm run test:stress -- <target>
勿把压测并入 E2E 或默认 CI。详见 stress-batch-tests.md.