跳转至

21 · 测试矩阵、CI 与发布

1,229 TestsVitestPlaywrightShardingCanary / Stable

一句话结论: Paperclip 的测试把“功能正确”拆成 shared contract、service 并发、workspace/provider、UI 行为、E2E 安装和 release smoke 多层;CI 通过分组/shard 和 serialized lane 控制耗时,发布脚本在打包、验证、标签和 smoke 之间形成闭环。

1. 测试规模与分布

快照中有约 1,229 个 test/spec 文件,粗略分布:

区域 代表内容
Server service、route、auth、recovery、tool、workspace、portability
UI component、page、query/cache、routing、adapter parser
Packages shared validators、DB helpers、adapter engines、plugin SDK
CLI command/API parity、onboard、doctor、context、worktree
Scripts release、migration、build、smoke、packaging contracts
Tests Playwright E2E、release smoke、visual/storybook

文件数不是质量指标,但体现了它把边界场景作为一等公民。

2. 测试层次

flowchart TB
  UNIT["Pure unit\nconstants / parser / validator"] --> SERVICE["Service integration\nDB + transactions + locks"]
  SERVICE --> ROUTE["Route/API\nauth + HTTP semantics"]
  ROUTE --> UI["Component/page\nquery + realtime + optimistic"]
  ROUTE --> E2E["Playwright E2E\ninstall + browser workflow"]
  BUILD["Package/build tests"] --> RELEASE["Docker/release smoke"]
  E2E --> RELEASE

选择层次的原则:

  • 纯逻辑用快速 unit;
  • 并发/数据库/权限在 service integration;
  • 浏览器交互只在 E2E;
  • 发布安装/容器启动必须有 smoke。

3. 不变量测试比 happy path 更重要

典型测试主题包括:

  • Issue checkout race/409/idempotent/stale adoption;
  • run status CAS 与 late failure;
  • budget hard stop 与 incident idempotency;
  • auth cross-company not-found;
  • workspace path traversal/symlink;
  • lease expiry/cleanup;
  • tool approval signed arguments;
  • plugin worker crash/restart/capability denial;
  • skills trust/import drift;
  • pipeline lease/version/automation ledger;
  • hot restart adoption/lost classification。

命名如 heartbeat-process-recovery.test.ts、execution-workspaces-service.test.ts、tool-gateway.test.ts 已经在告诉读者真正的风险面。

4. Shared Contract Tests

packages/shared 测试确保:

  • constants 与 type union 一致;
  • Zod validators 接受/拒绝边界;
  • config 跨字段规则;
  • issue/agent/approval/tool/trust 状态;
  • portability/frontmatter/zip/hash;
  • telemetry event 与 retention。

它们是跨 Server/UI/CLI 的早期护栏,避免把坏输入推到更慢的 E2E。

5. 数据库与 Service Integration

Server tests 使用真实或测试 PostgreSQL/embedded helper 验证:

  • transaction;
  • unique/check/index;
  • FOR UPDATE/CAS;
  • migration/backfill;
  • company isolation;
  • JSONB 配置;
  • filesystem/provider mock 与 DB operation ledger。

Mock DB 不能发现两个请求同时 checkout 的错误,所以并发不变量必须在有真实隔离级别的环境测。

6. UI 组件与缓存测试

UI 不只测“组件渲染出文字”,还测:

  • query key 与 placeholder data;
  • optimistic update/rollback;
  • live event patch/invalidation;
  • toast suppression;
  • company-prefixed routing;
  • adapter parser transcript;
  • visibility/focus;
  • mobile/responsive states。

LiveUpdatesProvider 把大量纯函数导出到 __liveUpdatesTestUtils,允许在不启动真实 WebSocket 时验证事件决策。

7. CLI Parity Tests

CLI 测试覆盖:

  • command registration/help;
  • context/profile precedence;
  • API request shape;
  • setup/doctor/onboard;
  • company/issue/agent/approval/skills/routine/pipeline;
  • secret and token behavior;
  • worktree/workspace;
  • output --json。

它们防止 CLI 只在 demo 中工作而与新 API schema 漂移。

8. E2E 与 Release Smoke 的区别

E2E

从源码构建后启动测试实例,使用 Playwright 验证 UI、安装、认证、MCP fixture、Issue 和 workspace 等用户流程。

Release Smoke

使用已发布 dist-tag/package + Docker onboarding,再验证真实安装产物和浏览器路径。它回答“发布出去的东西能不能从零启动”,不是“源码分支的功能是否通过”。

9. CI 分组与 Shard

Release verify workflow 把测试分成:

  • typecheck;
  • general server/workspace groups;
  • serialized tests 多 shard;
  • build;
  • 另有 E2E/visual/smoke workflow。

源仓库当前使用 Node 24、pnpm 9.15.4、actions/checkout@v7、actions/setup-node@v7 等现代 Action,并明确固定 lockfile/manifest 检查。

10. 为什么要 Serialized Tests

有些 Server tests:

  • 共享 embedded DB/端口;
  • 修改 instance settings;
  • 启动/停止外部进程;
  • 依赖 singleton runtime registry;
  • 修改全局 fixture。

它们不能简单并行。把 serialized 组单独 shard 既保留隔离,也能利用多个 runner。

11. E2E Shard 的隔离原则

Playwright lane 让每个 shard 启动自己的 Server,workers 受控为 1(某些 specs 改 instance flags)。这样并行发生在 runner 层,而不是同一个实例内互相踩状态。

12. Typecheck 不是唯一契约

Typecheck 会漏掉:

  • migration 未应用;
  • SQL index 缺失;
  • runtime adapter binary;
  • secret/provider 配置;
  • Docker 镜像/静态 UI packaging;
  • WebSocket proxy;
  • 真实浏览器焦点/resize。

因此 release verify 同时包含 build、test、E2E 和 smoke。

13. Release Package Map

Release scripts 维护 package map,检查:

  • 哪些 workspace 可发布;
  • package version/metadata;
  • private/example 包不误发布;
  • standalone/public package bootstrap;
  • generated files;
  • lockfile 与 manifest;
  • tags/release notes。

这对有 20+ workspace package 的 Monorepo 很关键:pnpm -r publish 过于粗糙。

14. Canary 与 Stable

发布流程分两段:

flowchart LR
  PUSH["master push"] --> VERIFY["release-verify reusable workflow"]
  VERIFY --> CANARY["publish canary + tag"]
  DISPATCH["manual stable dispatch"] --> VERIFY2["verify source_ref"]
  VERIFY2 --> PREVIEW["optional dry-run"]
  PREVIEW --> STABLE["publish stable + tag + GitHub Release"]

Canary 先验证并发布,stable 可由人工指定 ref/date,并可 dry-run 预览版本。

15. 发布前的清洁树与锁文件

Release script 要求 clean working tree。CI 会:

  • frozen/no-frozen install 按场景;
  • 检查 manifest/lockfile;
  • 必要时把 regenerated lockfile 放入临时 commit;
  • build/package;
  • 用 release script dry-run。

这避免“CI 生成了未提交文件,但 release 仍假设 clean”的隐性差异。

16. Docker Smoke

Release smoke:

  1. 构建/拉取发布 Docker 镜像;
  2. docker-onboard-smoke.sh 创建 throwaway data dir;
  3. 启动容器并读取 metadata;
  4. 用 Playwright 登录/操作;
  5. 上传报告、结果和 Docker logs;
  6. 清理容器。

它覆盖 onboarding、公开产物、默认配置和运行时镜像,不只是源码测试。

17. 失败诊断顺序

遇到 CI 红灯可以按层排查:

typecheck → shared/schema
service test → DB/concurrency/business invariant
UI test → cache/routing/component
build → package/generated asset
E2E → process/config/browser
release smoke → published artifact/Docker/onboard

不要把 E2E 失败优先解释为 UI bug;它可能是 migration、auth base URL 或 embedded DB 启动问题。

18. 仍需人工判断的地方

自动测试不能完全验证:

  • 新增 capability 是否符合 threat model;
  • recovery 是否会对错误任务产生副作用;
  • cost provider 归因是否正确;
  • UX 是否让 Board 理解 blocked/review;
  • 文档/规格是否反映代码现实。

因此每个大功能仍需要代码 review、文档更新和必要的 smoke/evidence。

19. 可复用经验

  1. 测试矩阵按不变量和生命周期分层,不只按目录。
  2. 并发/迁移/外部副作用要用 integration,不能全 mock。
  3. E2E 验证用户流程,release smoke 验证可安装发布产物。
  4. serialized lane 是对共享状态现实的承认,不是测试失败的借口。
  5. 发布前同时锁定 package map、lockfile、generated assets 和 clean tree。
  6. Canary→stable→Docker smoke 形成真实交付闭环。

源码入口

  • package.json、vitest.config.ts
  • server/src/__tests__/
  • ui/src/**/*.test.*
  • cli/src/__tests__/
  • scripts/__tests__/
  • .github/workflows/pr.yml
  • .github/workflows/release-verify.yml
  • .github/workflows/e2e.yml
  • .github/workflows/release-smoke.yml
  • scripts/release.sh、release-package-map.mjs

最后用一条任务时序把所有章节重新串起来:22 · 一次任务的端到端源码追踪。