diff --git a/TESTING.md b/TESTING.md index 688441bb..3c476f12 100644 --- a/TESTING.md +++ b/TESTING.md @@ -18,57 +18,72 @@ 测试分为三层,全部使用 **vitest** 运行: -``` +```text tests/ ├── e2e/ # E2E 集成测试(子进程运行真实 CLI) -│ ├── helpers.ts # runCli() 共享工具 -│ ├── public-commands.test.ts # 公开 API 命令(无需浏览器) +│ ├── helpers.ts # runCli() / parseJsonOutput() 共享工具 +│ ├── public-commands.test.ts # 公开 API 命令 │ ├── browser-public.test.ts # 浏览器命令(公开数据) -│ ├── browser-auth.test.ts # 需登录命令(graceful failure 测试) -│ ├── management.test.ts # 管理命令(list, validate, verify, help) -│ └── output-formats.test.ts # 输出格式(json/yaml/csv/md) -├── smoke/ # 烟雾测试(仅定时 / 手动触发) -│ └── api-health.test.ts # 外部 API 可用性检测 +│ ├── browser-auth.test.ts # 需登录命令(graceful failure) +│ ├── management.test.ts # 管理命令(list / validate / verify / help) +│ └── output-formats.test.ts # 输出格式校验 +├── smoke/ +│ └── api-health.test.ts # 外部 API、adapter 定义、命令注册健康检查 src/ -├── *.test.ts # 单元测试(已有 8 个) +└── **/*.test.ts # 单元测试(当前 31 个文件) ``` -| 层 | 位置 | 运行方式 | 用途 | -|---|---|---|---| -| 单元测试 | `src/**/*.test.ts` | `npx vitest run src/` | 内部模块逻辑 | -| E2E 测试 | `tests/e2e/*.test.ts` | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 | -| 烟雾测试 | `tests/smoke/*.test.ts` | `npx vitest run tests/smoke/` | 外部 API 健康 | +| 层 | 位置 | 当前文件数 | 运行方式 | 用途 | +|---|---|---:|---|---| +| 单元测试 | `src/**/*.test.ts` | 31 | `npx vitest run src/` | 内部模块、pipeline、adapter 工具函数 | +| E2E 测试 | `tests/e2e/*.test.ts` | 5 | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 | +| 烟雾测试 | `tests/smoke/*.test.ts` | 1 | `npx vitest run tests/smoke/` | 外部 API 与注册完整性 | --- ## 当前覆盖范围 -### 单元测试(8 个文件) +### 单元测试(31 个文件) -| 文件 | 覆盖内容 | +| 领域 | 文件 | |---|---| -| `browser.test.ts` | JSON-RPC、tab 管理、extension/standalone 模式切换 | -| `engine.test.ts` | 命令发现与执行 | -| `registry.test.ts` | 命令注册与策略分配 | -| `output.test.ts` | 输出格式渲染 | -| `doctor.test.ts` | Token 诊断 | -| `coupang.test.ts` | 数据归一化 | -| `pipeline/template.test.ts` | 模板表达式求值 | -| `pipeline/transform.test.ts` | 数据变换步骤 | - -### E2E 测试(~52 个用例) - -| 文件 | 覆盖站点/功能 | 测试数 | -|---|---|---| -| `public-commands.test.ts` | hackernews/top, v2ex/hot, v2ex/latest, v2ex/topic | 5 | -| `browser-public.test.ts` | bbc, bilibili×3, weibo, zhihu×2, reddit×2, twitter, xueqiu×2, reuters, youtube, smzdm, boss, ctrip, coupang, xiaohongshu, yahoo-finance, v2ex/daily | 21 | -| `browser-auth.test.ts` | bilibili/me,dynamic,favorite,history,following + twitter/bookmarks,timeline,notifications + v2ex/me,notifications + xueqiu/feed,watchlist + xiaohongshu/feed,notifications | 14 | -| `management.test.ts` | list×5 格式, validate×3 级别, verify, --version, --help, unknown cmd | 12 | -| `output-formats.test.ts` | json, yaml, csv, md 格式验证 | 5 | +| 核心运行时与输出 | `src/browser.test.ts`, `src/browser/dom-snapshot.test.ts`, `src/build-manifest.test.ts`, `src/capabilityRouting.test.ts`, `src/doctor.test.ts`, `src/engine.test.ts`, `src/interceptor.test.ts`, `src/output.test.ts`, `src/plugin.test.ts`, `src/registry.test.ts`, `src/snapshotFormatter.test.ts` | +| pipeline 与下载 | `src/download/index.test.ts`, `src/pipeline/executor.test.ts`, `src/pipeline/template.test.ts`, `src/pipeline/transform.test.ts` | +| 站点 / adapter 逻辑 | `src/clis/apple-podcasts/commands.test.ts`, `src/clis/apple-podcasts/utils.test.ts`, `src/clis/bloomberg/utils.test.ts`, `src/clis/chaoxing/utils.test.ts`, `src/clis/coupang/utils.test.ts`, `src/clis/google/utils.test.ts`, `src/clis/grok/ask.test.ts`, `src/clis/twitter/timeline.test.ts`, `src/clis/weread/utils.test.ts`, `src/clis/xiaohongshu/creator-note-detail.test.ts`, `src/clis/xiaohongshu/creator-notes-summary.test.ts`, `src/clis/xiaohongshu/creator-notes.test.ts`, `src/clis/xiaohongshu/user-helpers.test.ts`, `src/clis/xiaoyuzhou/utils.test.ts`, `src/clis/youtube/transcript-group.test.ts`, `src/clis/zhihu/download.test.ts` | + +这些测试覆盖的重点包括: + +- Browser Bridge、DOM snapshot、interceptor、capability routing +- manifest 生成、命令发现、插件安装与注册表 +- 输出格式渲染与 snapshot formatting +- pipeline 模板求值、执行器与变换步骤 +- 各站点 adapter 的数据归一化、参数处理与容错逻辑 + +### E2E 测试(5 个文件) + +| 文件 | 当前覆盖范围 | +|---|---| +| `tests/e2e/public-commands.test.ts` | `bloomberg`、`apple-podcasts`、`hackernews`、`v2ex`、`xiaoyuzhou`、`google suggest` 等公开命令 | +| `tests/e2e/browser-public.test.ts` | `bbc`、`bloomberg`、`bilibili`、`weibo`、`zhihu`、`reddit`、`twitter`、`xueqiu`、`reuters`、`youtube`、`smzdm`、`boss`、`ctrip`、`coupang`、`xiaohongshu`、`google`、`yahoo-finance`、`v2ex daily` | +| `tests/e2e/browser-auth.test.ts` | `bilibili`、`twitter`、`v2ex`、`xueqiu`、`linux-do`、`xiaohongshu` 的需登录命令 graceful failure | +| `tests/e2e/management.test.ts` | `list`、`validate`、`verify`、`--version`、`--help`、unknown command | +| `tests/e2e/output-formats.test.ts` | `json` / `yaml` / `csv` / `md` 输出格式校验 | + +### 烟雾测试(1 个文件) + +| 文件 | 当前覆盖范围 | +|---|---| +| `tests/smoke/api-health.test.ts` | `hackernews`、`v2ex` 公开 API 可用性,`validate` 全量 adapter 校验,以及命令注册表基础完整性 | -### 烟雾测试 +### 快速核对命令 -公开 API 可用性(hackernews, v2ex×2, v2ex/topic)+ 全站点注册完整性检查。 +需要刷新测试清单时,直接以仓库文件为准: + +```bash +find src -name '*.test.ts' | sort +find tests/e2e -name '*.test.ts' | sort +find tests/smoke -name '*.test.ts' | sort +``` --- @@ -78,7 +93,7 @@ src/ ```bash npm ci # 安装依赖 -npm run build # 编译(E2E 测试需要 dist/main.js) +npm run build # 编译(E2E / smoke 测试需要 dist/main.js) ``` ### 运行命令 @@ -87,18 +102,19 @@ npm run build # 编译(E2E 测试需要 dist/main.js) # 全部单元测试 npx vitest run src/ -# 全部 E2E 测试(会真实调用外部 API) +# 全部 E2E 测试(会真实调用外部 API / 浏览器) npx vitest run tests/e2e/ +# 全部 smoke 测试 +npx vitest run tests/smoke/ + # 单个测试文件 +npx vitest run src/clis/apple-podcasts/commands.test.ts npx vitest run tests/e2e/management.test.ts -# 全部测试(单元 + E2E) +# 全部测试 npx vitest run -# 烟雾测试 -npx vitest run tests/smoke/ - # watch 模式(开发时推荐) npx vitest src/ ``` @@ -106,9 +122,10 @@ npx vitest src/ ### 浏览器命令本地测试须知 - opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器 -- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass -- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过) -- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,手动跑对应测试 +- E2E 测试通过 `tests/e2e/helpers.ts` 里的 `runCli()` 调用已构建的 `dist/main.js` +- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬或地域限制导致空数据时会 warn + pass +- `browser-auth.test.ts` 验证 **graceful failure**,重点是不 crash、不 hang、错误信息可控 +- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,再手动运行对应测试 --- @@ -116,8 +133,8 @@ npx vitest src/ ### 新增 YAML Adapter(如 `src/clis/producthunt/trending.yaml`) -1. **无需额外操作**:`validate` 测试会自动覆盖 YAML 结构验证 -2. 根据 adapter 类型,在对应文件加一个 `it()` block: +1. `opencli validate` 的 E2E / smoke 测试会覆盖 adapter 结构校验 +2. 根据 adapter 类型,在对应测试文件补一个 `it()` block ```typescript // 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts @@ -148,15 +165,15 @@ it('producthunt me fails gracefully without login', async () => { ### 新增管理命令(如 `opencli export`) -在 `tests/e2e/management.test.ts` 添加测试。 +在 `tests/e2e/management.test.ts` 添加测试;如果新命令会影响输出格式,也同步补 `tests/e2e/output-formats.test.ts`。 ### 新增内部模块 -在 `src/` 下对应位置创建 `*.test.ts`。 +在对应源码旁创建 `*.test.ts`,优先和被测模块放在同一目录下,便于发现与维护。 ### 决策流程图 -``` +```text 新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts ↓ 否 是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts @@ -170,32 +187,33 @@ it('producthunt me fails gracefully without login', async () => { ## CI/CD 流水线 -### ci.yml(主流水线) +### `ci.yml` | Job | 触发条件 | 内容 | |---|---|---| -| **build** | push/PR to main,dev | typecheck + build | -| **unit-test** | push/PR to main,dev | 单元测试,2 shard 并行 | -| **smoke-test** | 每周一 08:00 UTC / 手动 | xvfb + real Chrome,外部 API 健康检查 | +| `build` | push/PR 到 `main`,`dev` | `tsc --noEmit` + `npm run build` | +| `unit-test` | push/PR 到 `main`,`dev` | Node `20` 与 `22` 双版本运行 `src/` 单元测试,按 `2` shard 并行 | +| `smoke-test` | `schedule` 或 `workflow_dispatch` | 安装真实 Chrome,`xvfb-run` 执行 `tests/smoke/` | -### e2e-headed.yml(E2E 测试) +### `e2e-headed.yml` | Job | 触发条件 | 内容 | |---|---|---| -| **e2e-headed** | push/PR to main,dev | xvfb + real Chrome,全部 E2E 测试 | +| `e2e-headed` | push/PR 到 `main`,`dev`,或手动触发 | 安装真实 Chrome,`xvfb-run` 执行 `tests/e2e/` | -E2E 使用 `browser-actions/setup-chrome` 安装真实 Chrome,配合 `xvfb-run` 提供虚拟显示器,以 headed 模式运行浏览器。 +E2E 与 smoke 都使用 `./.github/actions/setup-chrome` 准备真实 Chrome,并通过 `OPENCLI_BROWSER_EXECUTABLE_PATH` 注入浏览器路径。 ### Sharding -单元测试使用 vitest 内置 shard: +单元测试使用 vitest 内置 shard,并在 Node `20` / `22` 两个版本上运行: ```yaml strategy: matrix: + node-version: ['20', '22'] shard: [1, 2] steps: - - run: npx vitest run src/ --shard=${{ matrix.shard }}/2 + - run: npx vitest run src/ --reporter=verbose --shard=${{ matrix.shard }}/2 ``` --- @@ -206,8 +224,8 @@ opencli 通过 Browser Bridge 扩展连接浏览器: | 条件 | 模式 | 使用场景 | |---|---|---| -| 扩展已安装 | Extension 模式 | 本地用户,连接已登录的 Chrome | -| 扩展未安装 | CLI 报错提示安装 | 需要安装 Browser Bridge 扩展 | +| 扩展已安装 / 已连接 | Extension 模式 | 本地用户,连接已登录的 Chrome | +| 无扩展 token | CLI 自行拉起浏览器 | CI、无登录态或纯自动化场景 | CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径: @@ -220,14 +238,14 @@ env: ## 站点兼容性 -在 GitHub Actions 美国 runner 上,部分站点因地域限制或登录要求返回空数据。E2E 测试对这些站点使用 warn + pass 策略,不影响 CI 绿灯。 +GitHub Actions 的美国 runner 上,部分站点会因为地域限制、登录要求或反爬而返回空数据。当前 E2E 对这些场景采用 warn + pass 策略,避免偶发站点限制把整条 CI 打红。 -| 站点 | CI 状态 | 限制原因 | +| 站点 | CI 表现 | 常见原因 | |---|---|---| -| hackernews, bbc, v2ex | ✅ 返回数据 | 无限制 | -| yahoo-finance | ✅ 返回数据 | 无限制 | -| bilibili, zhihu, weibo, xiaohongshu | ⚠️ 空数据 | 地域限制(中国站点) | -| reddit, twitter, youtube | ⚠️ 空数据 | 需登录或 cookie | -| smzdm, boss, ctrip, coupang, xueqiu | ⚠️ 空数据 | 地域限制 / 需登录 | +| `hackernews`、`bbc`、`v2ex`、`bloomberg` | 通常返回数据 | 公开接口或公开页面 | +| `yahoo-finance`、`google` | 通常返回数据 | 页面公开,但仍可能受限流影响 | +| `bilibili`、`zhihu`、`weibo`、`xiaohongshu`、`xueqiu` | 容易空数据 | 地域限制、反爬、登录要求 | +| `reddit`、`twitter`、`youtube` | 容易空数据 | 登录态、cookie、机器人检测 | +| `smzdm`、`boss`、`ctrip`、`coupang`、`linux-do` | 结果波动较大 | 地域限制、风控或页面结构变动 | -> 使用 self-hosted runner(国内服务器)可解决地域限制问题。 +> 如果需要更稳定的浏览器 E2E 结果,优先使用具备目标站点网络可达性的 self-hosted runner。 diff --git a/docs/developer/testing.md b/docs/developer/testing.md index 1bd17c9b..9c98a8d6 100644 --- a/docs/developer/testing.md +++ b/docs/developer/testing.md @@ -18,57 +18,72 @@ 测试分为三层,全部使用 **vitest** 运行: -``` +```text tests/ ├── e2e/ # E2E 集成测试(子进程运行真实 CLI) -│ ├── helpers.ts # runCli() 共享工具 -│ ├── public-commands.test.ts # 公开 API 命令(无需浏览器) +│ ├── helpers.ts # runCli() / parseJsonOutput() 共享工具 +│ ├── public-commands.test.ts # 公开 API 命令 │ ├── browser-public.test.ts # 浏览器命令(公开数据) -│ ├── browser-auth.test.ts # 需登录命令(graceful failure 测试) -│ ├── management.test.ts # 管理命令(list, validate, verify, help) -│ └── output-formats.test.ts # 输出格式(json/yaml/csv/md) -├── smoke/ # 烟雾测试(仅定时 / 手动触发) -│ └── api-health.test.ts # 外部 API 可用性检测 +│ ├── browser-auth.test.ts # 需登录命令(graceful failure) +│ ├── management.test.ts # 管理命令(list / validate / verify / help) +│ └── output-formats.test.ts # 输出格式校验 +├── smoke/ +│ └── api-health.test.ts # 外部 API、adapter 定义、命令注册健康检查 src/ -├── *.test.ts # 单元测试(已有 8 个) +└── **/*.test.ts # 单元测试(当前 31 个文件) ``` -| 层 | 位置 | 运行方式 | 用途 | -|---|---|---|---| -| 单元测试 | `src/**/*.test.ts` | `npx vitest run src/` | 内部模块逻辑 | -| E2E 测试 | `tests/e2e/*.test.ts` | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 | -| 烟雾测试 | `tests/smoke/*.test.ts` | `npx vitest run tests/smoke/` | 外部 API 健康 | +| 层 | 位置 | 当前文件数 | 运行方式 | 用途 | +|---|---|---:|---|---| +| 单元测试 | `src/**/*.test.ts` | 31 | `npx vitest run src/` | 内部模块、pipeline、adapter 工具函数 | +| E2E 测试 | `tests/e2e/*.test.ts` | 5 | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 | +| 烟雾测试 | `tests/smoke/*.test.ts` | 1 | `npx vitest run tests/smoke/` | 外部 API 与注册完整性 | --- ## 当前覆盖范围 -### 单元测试(8 个文件) +### 单元测试(31 个文件) -| 文件 | 覆盖内容 | +| 领域 | 文件 | |---|---| -| `browser.test.ts` | JSON-RPC、tab 管理、extension/standalone 模式切换 | -| `engine.test.ts` | 命令发现与执行 | -| `registry.test.ts` | 命令注册与策略分配 | -| `output.test.ts` | 输出格式渲染 | -| `doctor.test.ts` | Token 诊断 | -| `coupang.test.ts` | 数据归一化 | -| `pipeline/template.test.ts` | 模板表达式求值 | -| `pipeline/transform.test.ts` | 数据变换步骤 | - -### E2E 测试(~52 个用例) - -| 文件 | 覆盖站点/功能 | 测试数 | -|---|---|---| -| `public-commands.test.ts` | hackernews/top, v2ex/hot, v2ex/latest, v2ex/topic | 5 | -| `browser-public.test.ts` | bbc, bilibili×3, weibo, zhihu×2, reddit×2, twitter, xueqiu×2, reuters, youtube, smzdm, boss, ctrip, coupang, xiaohongshu, yahoo-finance, v2ex/daily | 21 | -| `browser-auth.test.ts` | bilibili/me,dynamic,favorite,history,following + twitter/bookmarks,timeline,notifications + v2ex/me,notifications + xueqiu/feed,watchlist + xiaohongshu/feed,notifications | 14 | -| `management.test.ts` | list×5 格式, validate×3 级别, verify, --version, --help, unknown cmd | 12 | -| `output-formats.test.ts` | json, yaml, csv, md 格式验证 | 5 | +| 核心运行时与输出 | `src/browser.test.ts`, `src/browser/dom-snapshot.test.ts`, `src/build-manifest.test.ts`, `src/capabilityRouting.test.ts`, `src/doctor.test.ts`, `src/engine.test.ts`, `src/interceptor.test.ts`, `src/output.test.ts`, `src/plugin.test.ts`, `src/registry.test.ts`, `src/snapshotFormatter.test.ts` | +| pipeline 与下载 | `src/download/index.test.ts`, `src/pipeline/executor.test.ts`, `src/pipeline/template.test.ts`, `src/pipeline/transform.test.ts` | +| 站点 / adapter 逻辑 | `src/clis/apple-podcasts/commands.test.ts`, `src/clis/apple-podcasts/utils.test.ts`, `src/clis/bloomberg/utils.test.ts`, `src/clis/chaoxing/utils.test.ts`, `src/clis/coupang/utils.test.ts`, `src/clis/google/utils.test.ts`, `src/clis/grok/ask.test.ts`, `src/clis/twitter/timeline.test.ts`, `src/clis/weread/utils.test.ts`, `src/clis/xiaohongshu/creator-note-detail.test.ts`, `src/clis/xiaohongshu/creator-notes-summary.test.ts`, `src/clis/xiaohongshu/creator-notes.test.ts`, `src/clis/xiaohongshu/user-helpers.test.ts`, `src/clis/xiaoyuzhou/utils.test.ts`, `src/clis/youtube/transcript-group.test.ts`, `src/clis/zhihu/download.test.ts` | + +这些测试覆盖的重点包括: + +- Browser Bridge、DOM snapshot、interceptor、capability routing +- manifest 生成、命令发现、插件安装与注册表 +- 输出格式渲染与 snapshot formatting +- pipeline 模板求值、执行器与变换步骤 +- 各站点 adapter 的数据归一化、参数处理与容错逻辑 + +### E2E 测试(5 个文件) + +| 文件 | 当前覆盖范围 | +|---|---| +| `tests/e2e/public-commands.test.ts` | `bloomberg`、`apple-podcasts`、`hackernews`、`v2ex`、`xiaoyuzhou`、`google suggest` 等公开命令 | +| `tests/e2e/browser-public.test.ts` | `bbc`、`bloomberg`、`bilibili`、`weibo`、`zhihu`、`reddit`、`twitter`、`xueqiu`、`reuters`、`youtube`、`smzdm`、`boss`、`ctrip`、`coupang`、`xiaohongshu`、`google`、`yahoo-finance`、`v2ex daily` | +| `tests/e2e/browser-auth.test.ts` | `bilibili`、`twitter`、`v2ex`、`xueqiu`、`linux-do`、`xiaohongshu` 的需登录命令 graceful failure | +| `tests/e2e/management.test.ts` | `list`、`validate`、`verify`、`--version`、`--help`、unknown command | +| `tests/e2e/output-formats.test.ts` | `json` / `yaml` / `csv` / `md` 输出格式校验 | + +### 烟雾测试(1 个文件) + +| 文件 | 当前覆盖范围 | +|---|---| +| `tests/smoke/api-health.test.ts` | `hackernews`、`v2ex` 公开 API 可用性,`validate` 全量 adapter 校验,以及命令注册表基础完整性 | -### 烟雾测试 +### 快速核对命令 -公开 API 可用性(hackernews, v2ex×2, v2ex/topic)+ 全站点注册完整性检查。 +需要刷新测试清单时,直接以仓库文件为准: + +```bash +find src -name '*.test.ts' | sort +find tests/e2e -name '*.test.ts' | sort +find tests/smoke -name '*.test.ts' | sort +``` --- @@ -78,7 +93,7 @@ src/ ```bash npm ci # 安装依赖 -npm run build # 编译(E2E 测试需要 dist/main.js) +npm run build # 编译(E2E / smoke 测试需要 dist/main.js) ``` ### 运行命令 @@ -87,18 +102,19 @@ npm run build # 编译(E2E 测试需要 dist/main.js) # 全部单元测试 npx vitest run src/ -# 全部 E2E 测试(会真实调用外部 API) +# 全部 E2E 测试(会真实调用外部 API / 浏览器) npx vitest run tests/e2e/ +# 全部 smoke 测试 +npx vitest run tests/smoke/ + # 单个测试文件 +npx vitest run src/clis/apple-podcasts/commands.test.ts npx vitest run tests/e2e/management.test.ts -# 全部测试(单元 + E2E) +# 全部测试 npx vitest run -# 烟雾测试 -npx vitest run tests/smoke/ - # watch 模式(开发时推荐) npx vitest src/ ``` @@ -106,9 +122,10 @@ npx vitest src/ ### 浏览器命令本地测试须知 - opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器 -- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass -- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过) -- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,手动跑对应测试 +- E2E 测试通过 `tests/e2e/helpers.ts` 里的 `runCli()` 调用已构建的 `dist/main.js` +- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬或地域限制导致空数据时会 warn + pass +- `browser-auth.test.ts` 验证 **graceful failure**,重点是不 crash、不 hang、错误信息可控 +- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,再手动运行对应测试 --- @@ -116,8 +133,8 @@ npx vitest src/ ### 新增 YAML Adapter(如 `src/clis/producthunt/trending.yaml`) -1. **无需额外操作**:`validate` 测试会自动覆盖 YAML 结构验证 -2. 根据 adapter 类型,在对应文件加一个 `it()` block: +1. `opencli validate` 的 E2E / smoke 测试会覆盖 adapter 结构校验 +2. 根据 adapter 类型,在对应测试文件补一个 `it()` block ```typescript // 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts @@ -148,15 +165,15 @@ it('producthunt me fails gracefully without login', async () => { ### 新增管理命令(如 `opencli export`) -在 `tests/e2e/management.test.ts` 添加测试。 +在 `tests/e2e/management.test.ts` 添加测试;如果新命令会影响输出格式,也同步补 `tests/e2e/output-formats.test.ts`。 ### 新增内部模块 -在 `src/` 下对应位置创建 `*.test.ts`。 +在对应源码旁创建 `*.test.ts`,优先和被测模块放在同一目录下,便于发现与维护。 ### 决策流程图 -``` +```text 新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts ↓ 否 是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts @@ -170,33 +187,34 @@ it('producthunt me fails gracefully without login', async () => { ## CI/CD 流水线 -### ci.yml(主流水线) +### `ci.yml` | Job | 触发条件 | 内容 | |---|---|---| -| **build** | push/PR to main,dev | typecheck + build | -| **unit-test** | push/PR to main,dev | 单元测试,2 shard 并行 | -| **smoke-test** | 每周一 08:00 UTC / 手动 | xvfb + real Chrome,外部 API 健康检查 | +| `build` | push/PR 到 `main`,`dev` | `tsc --noEmit` + `npm run build` | +| `unit-test` | push/PR 到 `main`,`dev` | Node `20` 与 `22` 双版本运行 `src/` 单元测试,按 `2` shard 并行 | +| `smoke-test` | `schedule` 或 `workflow_dispatch` | 安装真实 Chrome,`xvfb-run` 执行 `tests/smoke/` | -### e2e-headed.yml(E2E 测试) +### `e2e-headed.yml` | Job | 触发条件 | 内容 | |---|---|---| -| **e2e-headed** | push/PR to main,dev | xvfb + real Chrome,全部 E2E 测试 | +| `e2e-headed` | push/PR 到 `main`,`dev`,或手动触发 | 安装真实 Chrome,`xvfb-run` 执行 `tests/e2e/` | -E2E 使用 `browser-actions/setup-chrome` 安装真实 Chrome,配合 `xvfb-run` 提供虚拟显示器,以 headed 模式运行浏览器。 +E2E 与 smoke 都使用 `./.github/actions/setup-chrome` 准备真实 Chrome,并通过 `OPENCLI_BROWSER_EXECUTABLE_PATH` 注入浏览器路径。 ### Sharding -单元测试使用 vitest 内置 shard: +单元测试使用 vitest 内置 shard,并在 Node `20` / `22` 两个版本上运行: ::: v-pre ```yaml strategy: matrix: + node-version: ['20', '22'] shard: [1, 2] steps: - - run: npx vitest run src/ --shard=${{ matrix.shard }}/2 + - run: npx vitest run src/ --reporter=verbose --shard=${{ matrix.shard }}/2 ``` ::: @@ -208,8 +226,8 @@ opencli 通过 Browser Bridge 扩展连接浏览器: | 条件 | 模式 | 使用场景 | |---|---|---| -| 扩展已安装 | Extension 模式 | 本地用户,连接已登录的 Chrome | -| 扩展未安装 | CLI 报错提示安装 | 需要安装 Browser Bridge 扩展 | +| 扩展已安装 / 已连接 | Extension 模式 | 本地用户,连接已登录的 Chrome | +| 无扩展 token | CLI 自行拉起浏览器 | CI、无登录态或纯自动化场景 | CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径: @@ -224,14 +242,14 @@ env: ## 站点兼容性 -在 GitHub Actions 美国 runner 上,部分站点因地域限制或登录要求返回空数据。E2E 测试对这些站点使用 warn + pass 策略,不影响 CI 绿灯。 +GitHub Actions 的美国 runner 上,部分站点会因为地域限制、登录要求或反爬而返回空数据。当前 E2E 对这些场景采用 warn + pass 策略,避免偶发站点限制把整条 CI 打红。 -| 站点 | CI 状态 | 限制原因 | +| 站点 | CI 表现 | 常见原因 | |---|---|---| -| hackernews, bbc, v2ex | ✅ 返回数据 | 无限制 | -| yahoo-finance | ✅ 返回数据 | 无限制 | -| bilibili, zhihu, weibo, xiaohongshu | ⚠️ 空数据 | 地域限制(中国站点) | -| reddit, twitter, youtube | ⚠️ 空数据 | 需登录或 cookie | -| smzdm, boss, ctrip, coupang, xueqiu | ⚠️ 空数据 | 地域限制 / 需登录 | +| `hackernews`、`bbc`、`v2ex`、`bloomberg` | 通常返回数据 | 公开接口或公开页面 | +| `yahoo-finance`、`google` | 通常返回数据 | 页面公开,但仍可能受限流影响 | +| `bilibili`、`zhihu`、`weibo`、`xiaohongshu`、`xueqiu` | 容易空数据 | 地域限制、反爬、登录要求 | +| `reddit`、`twitter`、`youtube` | 容易空数据 | 登录态、cookie、机器人检测 | +| `smzdm`、`boss`、`ctrip`、`coupang`、`linux-do` | 结果波动较大 | 地域限制、风控或页面结构变动 | -> 使用 self-hosted runner(国内服务器)可解决地域限制问题。 +> 如果需要更稳定的浏览器 E2E 结果,优先使用具备目标站点网络可达性的 self-hosted runner。