研发协作
在 CI/CD 流水线中运行测试套件时…
为 GitHub Actions 或 GitLab CI 设计按触发条件运行正确测试、分片并行、上传证据产物、隔离 flaky 测试并以覆盖率门禁阻止合并的流水线。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 CI/CD 流水线中运行测试套件时,通用
解决思路
- 先检查 `.agents/qa-project-context.md`,若存在则直接使用其中已确认的信息(尤其是 `team_maturity` 和现有 CI 约定),跳过已回答的问题。
- 确认 CI 平台(GitHub Actions、GitLab CI、CircleCI、Jenkins),本技能提供 GitHub Actions 和 GitLab CI 模板。
- 确认需要运行的测试类型(单元、集成、E2E、视觉、性能),不同类型对资源和耗时需求不同。
- 确认当前 CI 时长:超过 10 分钟则并行与分片是必需而非可选。
- 确认每日推送的开发者数量:高频团队需要更激进的并发取消和缓存策略。
- 先映射触发条件与测试套件,再写 YAML:并非每次推送都需要完整 E2E 套件。
- 按团队成熟度选择流水线形态:startup 用单 job(lint + unit + 一个 E2E smoke 于 PR);growing 拆分 unit/integration/E2E job 并加入并行、产物上传、结果发布、flaky 隔离;established 使用完整矩阵(分片 E2E、多环境晋级门禁、性能与安全扫描、部署门禁、SLA 流水线)。
- 遵循快速反馈原则:每次推送跑单元测试(2 分钟内),PR 跑 E2E(10 分钟内),合并与夜间跑完整套件。
- 并行优先:将测试分片到多个 worker,例如 20 分钟串行套件在 4 个分片下变为 5 分钟。
- 把产物当作证据:每次运行都存储 trace、截图、覆盖率和 HTML 报告,否则 CI 失败会变成无法调试的“本地复现”循环。
- 对 flaky 测试进行隔离而非重试:重试会掩盖问题,应将 flaky 测试移入非阻塞 job、跟踪并修复根因。
- 质量门禁越接近生产越严格:PR 门禁快速廉价,部署门禁全面。
- 覆盖率阈值从配置读取而非 bash 脚本:让测试运行器通过自身的 `coverageThreshold`/`thresholds` 强制并在不达标时以非零退出,避免用正则从 stdout 抓取百分比。
- 按触发条件映射测试:推送到分支跑 lint、type-check、unit(最多 2 分钟);PR 打开/更新加 integration、E2E smoke(最多 10 分钟);合并到 main 加完整 E2E、视觉、性能预算(最多 15 分钟);夜间定时跑完整套件、安全、a11y、flaky 隔离(最多 30 分钟);发布 tag 跑完整套件并对 staging 做 smoke(最多 20 分钟)。
- 在 GitHub Actions 中固定 action 主版本并让 Dependabot 升级:`actions/checkout@v6`、`actions/setup-node@v6`、`actions/cache@v5`、`actions/upload-artifact@v7`、`actions/download-artifact@v7`、`dorny/test-reporter@v3`、`dorny/paths-filter@v3`、`marocchino/sticky-pull-request-comment@v3`、`slackapi/slack-github-action@v2`。
- 对供应链敏感的流水线,将第三方 action(dorny、marocchino、slackapi、knapsack)固定到完整 40 位 commit SHA 并附版本注释,让 Dependabot 更新 SHA;第一方 `actions/*` 风险较低,可用 tag。
- 使用并发组取消浪费的运行:`concurrency: { group: tests-${{ github.ref }}, cancel-in-progress: true }`。
- 使用矩阵分片跨 runner 运行:`strategy: { fail-fast: false, matrix: { shard: [1,2,3,4] } }`,步骤执行 `npx playwright test --shard=${{ matrix.shard }}/4`。
- 缓存 Playwright 浏览器避免每次重新下载:用 `actions/setup-node@v6` 配 `cache: npm`,再用 `actions/cache@v5` 缓存 `~/.cache/ms-playwright`,key 为 `playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}`,仅在缓存未命中时执行 `npx playwright install --with-deps chromium`。
- 上传报告与 trace 产物,并用 `actions/download-artifact@v7` 配 `pattern: test-results-*` 合并分片报告,再执行 `npx playwright merge-reports --reporter=html`。
- 超过 10–15 个分片时使用基于历史耗时的均衡器:`knapsack-pro`、CloudBees Smart Tests(原 Launchable)、Datadog Test Optimization、Trunk Flaky Tests;在此之前 Playwright 的 `--shard` 已按文件并按历史耗时均衡,可用 `npx playwright test --reporter=json | jq` 导出耗时数据,Jest 可用 `jest-slow-test-reporter`。
- 自托管 Kubernetes runner 使用 Actions Runner Controller(`arc-runner-set` / `gha-runner-scale-set`),通过 Helm 安装并按 workflow 自动扩缩 runner pod,替代已弃用的 `runner-deployment` CRD。
- 在 Settings → Branches → Branch protection rules 中保护 main:启用 “Require status checks to pass before merging”,将 `lint`、`unit-tests`、`e2e`(所有分片)设为必需检查,并启用 “Require branches to be up to date”。
- GitLab CI 使用 stages `[validate, test, e2e, deploy]`,lint/unit 用 `node:22-alpine`,E2E 用 `mcr.microsoft.com/playwright:v1.60.0-noble`(与安装的 `@playwright/test` minor 保持一致)。
- GitLab CI 并行分片:`parallel: 4` 暴露 `CI_NODE_INDEX`/`CI_NODE_TOTAL`,执行 `npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL`。
- GitLab CI 覆盖率:输出 cobertura `coverage_report` 产物和 `junit` 报告,让 GitLab 从中读取百分比与测试结果,避免依赖脆弱的 `coverage:` stdout 正则。
- 向 PR 发布测试结果:使用 `dorny/test-reporter@v3`,条件 `if: ${{ !cancelled() }}`,指定 `path: test-results/junit.xml` 和 `reporter: jest-junit`(Playwright JUnit 报告用 `java-junit`)。
- 使用 `dorny/paths-filter@v3` 设置输出并据此门控步骤,实现只测试变更部分的条件执行。
- 隔离 flaky 测试:稳定 job 执行 `npx playwright test --grep-invert @flaky` 并作为必需检查;隔离 job 设 `continue-on-error: true` 执行 `npx playwright test --grep @flaky`,失败时输出 `::warning::`。
- 在源码中给 flaky 测试打标签(如 `test('... @flaky', ...)`)以便 grep 拆分;若隔离测试连续 10 次通过则移除 `@flaky` 标签。
- 按层缓存:Node modules 由 `setup-node` 的 `cache: npm` 自动处理;Playwright 浏览器缓存 `~/.cache/ms-playwright`,key 为 `pw-{os}-{hash(package-lock.json)}`;Next.js 构建缓存 `.next/cache`,key 为 `nextjs-{os}-{hash(lockfile)}-{hash(src)}`;测试 fixture 缓存 `e2e/fixtures/.cache`,key 为 `test-data-{hash(seed.sql)}`;第 2–4 层用 `actions/cache@v5`,构建缓存加 `restore-keys` 支持部分匹配。
- 使用 OIDC 无密钥部署,不存储长期 `DEPLOY_TOKEN`:deploy job 设 `permissions: { id-token: write, contents: read }`,用 `aws-actions/configure-aws-credentials@v6` 指定 `role-to-assume` 和 `aws-region`,再执行部署脚本使用短期 STS 凭证;IAM 角色信任策略将 `sub` 声明固定到仓库与分支;GCP 用 `google-github-actions/auth`,Azure 用 `azure/login`。
- 失败通知使用 `slackapi/slack-github-action@v2` 配 `webhook-type: incoming-webhook`,条件 `if: failure() && github.ref == 'refs/heads/main'` 仅通知 main 分支失败;升级到 `@v3` 前注意其 workflow-trigger webhook 的 payload 处理已变更(不再扁平化/字符串化)。
- 设置质量门禁:PR Gate(PR 打开/更新,必需 lint、type-check、unit、覆盖率阈值,阻塞,3 分钟内);Merge Gate(合并前,加 E2E smoke,阻塞,10 分钟内);Deploy Gate(生产部署前,加完整 E2E、视觉、性能预算,阻塞,15 分钟内);Nightly Gate(每日 2am,完整套件、npm audit、axe a11y,仅告警,30 分钟内)。
- 在测试运行器配置中强制覆盖率下限,例如 `jest.config.js` 中 `coverageThreshold: { global: { lines: 80, statements: 80, branches: 70 } }`,`jest --coverage` 低于阈值时以非零退出;若需打印数值,让 Jest 输出 `json-summary` 并读取 `coverage/coverage-summary.json`(用 `jq '.total.lines.pct'`),nyc/c8 项目用 `nyc report --reporter=text-summary`,不要使用已弃用的 `istanbul report`。
- Deploy Gate 需 `[unit-tests, e2e-tests, visual-tests]`,再执行性能预算检查(`npx lhci autorun` / `lhci assert --config=lighthouserc.json`),然后执行 OIDC 部署。
- Nightly Gate 将安全与 a11y 步骤写成真实 job:`npm audit --audit-level=high` 在高危/严重漏洞时失败;`npx playwright test --grep @a11y` 运行使用 `@axe-core/playwright` 的 spec(如 `new AxeBuilder({ page }).analyze()` 并断言 `results.violations` 为空);结果发到 Slack 而非阻塞检查。
- 避免反模式:不要在每次提交跑全部测试;不要不存产物(应在 `if: ${{ !cancelled() }}` 时上传);不要重试 flaky 测试而不跟踪;不要有仅 CI 失败却无法本地复现(记录原因并用与 CI 相同的 Playwright 镜像提供本地复现脚本);不要在 CI job 间共享状态(用 `upload-artifact`/`download-artifact` 和 `needs` 传递数据);不要缺少并发控制;不要在 workflow 中硬编码密钥(用 `${{ secrets.X }}` 或 GitLab CI/CD 变量,优先 OIDC);不要忽略 job 超时(每个 job 设 `timeout-minutes`,Playwright 配置设 `actionTimeout`/`navigationTimeout`)。
- 验证流水线:先运行 `actionlint .github/workflows/*.yml` 检查表达式、`needs` 和 shell 引号错误,并加 `yamllint .github/workflows/` 检查缩进,同时把 actionlint 作为 job 运行。
- 用 `act -j unit-tests` 在容器中本地试跑 job,无需推送即可迭代。
- 推送到临时分支并打开草稿 PR,确认 `lint`、`unit-tests`、`e2e` 等预期检查出现,并确认覆盖率低于阈值时门禁失败。
- 从 Actions UI 或 `gh run download <id>` 下载产物,确认 `playwright-report/` 和 trace 存在。
- 完成标准:存在触发映射(推送只跑 lint+unit,PR workflow 用 `if: github.event_name == 'pull_request'` 门控 E2E,需验证 YAML);`actionlint .github/workflows/*.yml` 退出码为 0;非阻塞隔离 job 用 `--grep @flaky` 配 `continue-on-error: true`,稳定 job 用 `--grep-invert @flaky` 且在必需检查列表中;测试产物在 `if: ${{ !cancelled() }}` 时上传并显式设置 `retention-days`/`expire_in`;PR/测试 workflow 设置并发组与 `cancel-in-progress: true`;覆盖率由运行器的 `coverageThreshold`/`thresholds` 强制(低于下限时 job 非零退出),无 `coverage-summary` CLI 抓取;分支保护列出 `lint`、`unit-tests`、`e2e` 为必需状态检查;YAML 中无长期部署 token,部署使用 OIDC(`id-token: write` + 云角色)或至少引用密钥存储。
适用边界 · 注意事项
- 不适用于单个 flaky 测试的运行时自愈(选择器恢复、自动重试策略)——应使用 test-reliability。
- 不适用于发布 go/no-go 决策与 smoke 测试清单——应使用 release-readiness。
- 不适用于测试结果仪表盘与趋势报告——应使用 qa-metrics。