Files
jackwener__opencli/TESTING.md
AlexYue 78d52d984b test(plugin): add E2E integration tests for plugin lifecycle (#389)
* test(plugin): add E2E integration tests for plugin lifecycle

Add plugin-management.test.ts covering the full plugin lifecycle
using real GitHub clone of opencli-plugin-hot-digest:
- plugin install from github:ByteYue/opencli-plugin-hot-digest
- plugin list (table and JSON formats)
- plugin update on installed plugin
- plugin uninstall with cleanup verification
- error paths: invalid source, non-existent plugin, missing args

Tests safely backup/restore existing plugin state to avoid
interfering with user's real installed plugins.

Update TESTING.md to document the new test file.

* test(plugin): isolate lifecycle e2e from user home

* fix(plugin): respect HOME env var for test isolation

The E2E tests for plugin management were failing because os.homedir()
doesn't respect the HOME environment variable. This made test isolation
impossible since all tests would use the real ~/.opencli directory.

Added getHomeDir() helper that checks process.env.HOME first before
falling back to os.homedir(). Updated readLockFile() and writeLockFile()
to use this new function.

Fixes test failures in plugin-management.test.ts where:
- plugin install would write to real home instead of temp dir
- lock file assertions would fail with ENOENT

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-25 00:23:51 +08:00

10 KiB
Raw Permalink Blame History

Testing Guide

面向开发者和 AI Agent 的测试参考手册。

目录


测试架构

测试分为三层,全部使用 vitest 运行:

tests/
├── e2e/                           # E2E 集成测试(子进程运行真实 CLI)
│   ├── 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     # 输出格式校验
├── smoke/
│   └── api-health.test.ts         # 外部 API、adapter 定义、命令注册健康检查
src/
└── **/*.test.ts                   # 单元测试(当前 32 个文件)
层 位置 当前文件数 运行方式 用途
单元测试 src/**/*.test.ts 32 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 与注册完整性

当前覆盖范围

单元测试(32 个文件)

领域 文件
核心运行时与输出 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/search.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 输出格式校验
tests/e2e/plugin-management.test.ts plugin install / list / update / uninstall 全生命周期

烟雾测试(1 个文件)

文件 当前覆盖范围
tests/smoke/api-health.test.ts hackernews、v2ex 公开 API 可用性,validate 全量 adapter 校验,以及命令注册表基础完整性

快速核对命令

需要刷新测试清单时,直接以仓库文件为准:

find src -name '*.test.ts' | sort
find tests/e2e -name '*.test.ts' | sort
find tests/smoke -name '*.test.ts' | sort

本地运行测试

前置条件

npm ci                # 安装依赖
npm run build         # 编译(E2E / smoke 测试需要 dist/main.js)

运行命令

# 全部单元测试
npx vitest run src/

# 全部 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

# 全部测试
npx vitest run

# watch 模式(开发时推荐)
npx vitest src/

浏览器命令本地测试须知

  • opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器
  • 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 扩展,再手动运行对应测试

如何添加新测试

新增 YAML Adapter(如 src/clis/producthunt/trending.yaml)

  1. opencli validate 的 E2E / smoke 测试会覆盖 adapter 结构校验
  2. 根据 adapter 类型,在对应测试文件补一个 it() block
// 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts
it('producthunt trending returns data', async () => {
  const { stdout, code } = await runCli(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
  expect(code).toBe(0);
  const data = parseJsonOutput(stdout);
  expect(Array.isArray(data)).toBe(true);
  expect(data.length).toBeGreaterThanOrEqual(1);
  expect(data[0]).toHaveProperty('title');
}, 30_000);
// 如果 browser: true 但可公开访问 → tests/e2e/browser-public.test.ts
it('producthunt trending returns data', async () => {
  const data = await tryBrowserCommand(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
  expectDataOrSkip(data, 'producthunt trending');
}, 60_000);
// 如果 browser: true 且需登录 → tests/e2e/browser-auth.test.ts
it('producthunt me fails gracefully without login', async () => {
  await expectGracefulAuthFailure(['producthunt', 'me', '-f', 'json'], 'producthunt me');
}, 60_000);

新增管理命令(如 opencli export)

在 tests/e2e/management.test.ts 添加测试;如果新命令会影响输出格式,也同步补 tests/e2e/output-formats.test.ts。

新增内部模块

在对应源码旁创建 *.test.ts,优先和被测模块放在同一目录下,便于发现与维护。

决策流程图

新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts
                ↓ 否
         是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts
                              ↓ true
                        公开数据? → tests/e2e/browser-public.test.ts
                              ↓ 需登录
                        tests/e2e/browser-auth.test.ts

CI/CD 流水线

ci.yml

Job 触发条件 内容
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

Job 触发条件 内容
e2e-headed push/PR 到 main,dev,或手动触发 安装真实 Chrome,xvfb-run 执行 tests/e2e/

E2E 与 smoke 都使用 ./.github/actions/setup-chrome 准备真实 Chrome,并通过 OPENCLI_BROWSER_EXECUTABLE_PATH 注入浏览器路径。

Sharding

单元测试使用 vitest 内置 shard,并在 Node 20 / 22 两个版本上运行:

strategy:
  matrix:
    node-version: ['20', '22']
    shard: [1, 2]
steps:
  - run: npx vitest run src/ --reporter=verbose --shard=${{ matrix.shard }}/2

浏览器模式

opencli 通过 Browser Bridge 扩展连接浏览器:

条件 模式 使用场景
扩展已安装 / 已连接 Extension 模式 本地用户,连接已登录的 Chrome
无扩展 token CLI 自行拉起浏览器 CI、无登录态或纯自动化场景

CI 中使用 OPENCLI_BROWSER_EXECUTABLE_PATH 指定真实 Chrome 路径:

env:
  OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}

站点兼容性

GitHub Actions 的美国 runner 上,部分站点会因为地域限制、登录要求或反爬而返回空数据。当前 E2E 对这些场景采用 warn + pass 策略,避免偶发站点限制把整条 CI 打红。

站点 CI 表现 常见原因
hackernews、bbc、v2ex、bloomberg 通常返回数据 公开接口或公开页面
yahoo-finance、google 通常返回数据 页面公开,但仍可能受限流影响
bilibili、zhihu、weibo、xiaohongshu、xueqiu 容易空数据 地域限制、反爬、登录要求
reddit、twitter、youtube 容易空数据 登录态、cookie、机器人检测
smzdm、boss、ctrip、coupang、linux-do 结果波动较大 地域限制、风控或页面结构变动

如果需要更稳定的浏览器 E2E 结果,优先使用具备目标站点网络可达性的 self-hosted runner。