关于 ci-cd-integration
ci-cd-integration 是用于设计测试流水线的 QA Skill,面向希望提升 CI/CD 测试执行效率的团队。内容涵盖按触发条件选择测试、分片执行、保存产物、隔离不稳定测试及质量门禁,并提供 GitHub Actions 和 GitLab CI 模板。
主要功能
- 01按流水线触发条件安排测试
- 02在多个 Runner 间分片测试
- 03将报告和追踪保存为产物
- 04隔离不稳定测试
- 05设置覆盖率与合并门禁
- 06提供 GitHub Actions 和 GitLab CI 模板
适用场景
- 01缩短耗时较长的 CI 测试套件
- 02为 Pull Request 发布测试报告
- 03避免不稳定测试阻塞合并
- 04依据测试与覆盖率结果设置发布门禁
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill ci-cd-integration安装后在 Claude Code 中输入/ci-cd-integration确认已加载。
附带文件references/github-actions-templates.mdreferences/gitlab-ci-template.md
详细介绍
调研问题
先检查 .agents/qa-project-context.md;如果文件存在,则使用其中的信息,并跳过已回答的问题(尤其是 team_maturity 和现有 CI 规范)。然后:
- 使用哪种 CI 平台? GitHub Actions、GitLab CI、CircleCI、Jenkins?本技能提供 GitHub Actions 和 GitLab CI 模板。
- 需要运行哪些测试类型? 单元测试、集成测试、E2E、视觉测试、性能测试?每种测试的资源和耗时需求都不同。
- 目前 CI 耗时多久? 超过 10 分钟就必须并行执行和分片,而非可选项。
- 开发者每天推送多少次? 推送频繁的团队需要积极取消并发任务并使用缓存。
- 哪些触发条件应运行哪些测试? 并非每次推送都需要运行完整的 E2E 测试套件——编写 YAML 前先将触发条件映射到测试套件。
根据团队成熟度调整
在 .agents/qa-project-context.md 中设置 team_maturity;选择与之匹配的流水线结构:
- startup —— 一个任务:在 PR 上运行 lint、单元测试和一次 E2E 冒烟测试。优先快速反馈,而非完整覆盖。
- growing —— 单元测试、集成测试和 E2E 分设任务。并行执行、上传工件、发布结果、隔离不稳定测试。
- established —— 完整矩阵:分片运行 E2E、多环境晋级门禁、性能与安全扫描、部署门禁检查、由 SLA 保障的流水线。
核心原则
- 快速反馈:在合适的时机运行合适的测试。 每次推送都运行单元测试(少于 2 分钟)。PR 上运行 E2E(少于 10 分钟)。合并时和每晚运行完整测试套件。下方的触发条件到测试套件映射表就是约定。
- 优先并行:将测试分片并分配给多个工作器。 串行运行需 20 分钟的测试套件,分成 4 个分片后只需 5 分钟。运行器成本总是值得的。
- 工件就是证据。 每次运行都保存跟踪记录、截图、覆盖率和 HTML 报告。没有工件,CI 失败就会陷入无法调试的“在本地复现”循环。
- 不稳定测试应隔离,而非重试。 重试会掩盖问题——测试重试后通过,报告显示绿色,但竞态条件依然存在。将不稳定测试移到不阻塞流程的任务中,跟踪它们并修复根本原因。
- 越接近生产环境,质量门槛越严格。 明确每个阶段必须通过哪些检查;PR 门禁应快速且成本低,部署门禁则应全面。
- 从配置中读取阈值,而非从 bash 中读取。 让测试运行器通过自身的
coverageThreshold/thresholds强制执行覆盖率要求,并以非零状态码退出。使用正则表达式从标准输出中提取百分比,在不同运行器版本间容易出错。
流水线架构
Push to branch: lint+types (30s) → unit (1-2m)
PR opened: + integration (2-3m) → E2E sharded (5-8m) → merge report
Merge to main: full E2E ∥ visual ∥ perf budget → deploy (OIDC)
Nightly (cron): full suite + npm audit + axe a11y + flaky quarantine
何时运行哪些测试
| 触发条件 | 测试 | 最长耗时 |
|---|---|---|
| 推送到分支 | lint、类型检查、单元测试 | 2 分钟 |
| PR 创建或更新 | + 集成测试、E2E 冒烟测试 | 10 分钟 |
| 合并到 main | + 完整 E2E、视觉测试、性能预算 | 15 分钟 |
| 夜间定时任务 | 完整测试套件、安全检查、a11y、不稳定测试隔离 | 30 分钟 |
| 发布标签 | 完整测试套件、针对 staging 的冒烟测试 | 20 分钟 |
GitHub Actions
完整的可直接复制粘贴的工作流文件(单元测试、分片运行的 Playwright E2E、完整流水线、夜间任务、PR 门禁)请参阅 references/github-actions-templates.md。
Action 版本(2026 年 6 月)
固定到当前主版本,并让 Dependabot 为其升级。actions/* 系列在 Node 24 运行器上运行;Node 20 已在 GitHub 托管的运行器上弃用。
| Action | 当前主版本 | 备注 |
|---|---|---|
actions/checkout | @v6 | |
actions/setup-node | @v6 | v5+ 仅在设置了 packageManager 时自动缓存;使用 cache: npm 明确指定 |
actions/cache | @v5 | 新缓存服务 v2 后端 |
actions/upload-artifact | @v7 | v7 可以上传未压缩的文件(archive: false) |
actions/download-artifact | @v7 | 与相同主版本的 upload-artifact 配对使用 |
dorny/test-reporter | @v3 | v3 需要 Node 24 运行器;报告器键保持不变 |
dorny/paths-filter | @v3 | |
marocchino/sticky-pull-request-comment | @v3 | |
slackapi/slack-github-action | @v2 | 浮动主版本;采用 v3 前请参阅通知说明 |
对于对供应链安全敏感的流水线,请将第三方 Action(dorny、marocchino、slackapi、knapsack)固定到完整长度的提交 SHA,并添加版本注释,再让 Dependabot 更新 SHA:uses: dorny/test-reporter@<40-char-sha> # v3.0.0。第一方 actions/* 风险较低;在这类 Action 中使用标签即可。
核心概念
并发组:当某个分支有多次推送时,取消浪费资源的运行:
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true
矩阵分片:在多个运行器间拆分:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4
缓存浏览器,避免每次运行都重新下载:
- uses: actions/setup-node@v6
with: { node-version: 22, cache: npm }
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
构件用于保存报告和跟踪记录,以及将分片报告合并为一个 HTML 报告——请参阅 references/github-actions-templates.md(E2E 工作流)。合并作业使用 actions/download-artifact@v7 和 pattern: test-results-*,然后运行 npx playwright merge-reports --reporter=html。
大规模场景下的更智能分片
超过 10–15 个分片后,简单的基于哈希拆分会因分片负载不均而浪费运行器时间。请使用基于耗时的均衡器:
knapsack-pro——基于耗时数据,支持 Playwright/Jest/Cypress/RSpec;按历史运行时长分配。- CloudBees Smart Tests(原名 Launchable)——机器学习优先级排序 + 测试影响分析;仅运行差异中可能失败的测试。
- Datadog Test Optimization——测试影响分析 + 不稳定测试管理;根据历史耗时均衡分片。
- Trunk Flaky Tests——基于不稳定测试的隔离 + 重试预算管理。
在考虑付费均衡器之前:Playwright 的 --shard 已会按文件分配,并依据此前运行的耗时进行均衡。要检查或提供自定义耗时数据,请自行导出:npx playwright test --reporter=json | jq '[.suites[].specs[] | {file: .file, duration: .tests[].results[].duration}]'。对于 Jest,jest-slow-test-reporter 会列出耗时最长的测试用例,便于拆分或修复。
对于运行在 Kubernetes 上的可自托管运行器,请使用 Actions Runner Controller(arc-runner-set / gha-runner-scale-set)——通过 Helm 安装,并按工作流自动扩缩运行器 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
完整流水线请参阅 references/gitlab-ci-template.md。要点:
- 阶段为
[validate, test, e2e, deploy];lint/unit 使用node:22-alpine,E2E 使用mcr.microsoft.com/playwright:v1.60.0-noble(将此版本固定为与你安装的@playwright/test次版本一致)。 - 并行分片:
parallel: 4会提供CI_NODE_INDEX/CI_NODE_TOTAL;运行npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL。 - 覆盖率:生成 cobertura
coverage_report构件和junit报告;GitLab 会从中读取百分比和测试结果。旧版coverage:标准输出正则表达式在不同 Jest 版本间不够可靠,建议优先使用 cobertura 报告。
高级模式
将测试结果发布到 PR 评论
- name: Publish test results
uses: dorny/test-reporter@v3
if: ${{ !cancelled() }}
with:
name: Test Results
path: test-results/junit.xml
reporter: jest-junit # use java-junit for a Playwright JUnit report
关于置顶覆盖率 PR 评论(marocchino/sticky-pull-request-comment@v3),请参阅 references/github-actions-templates.md(PR 质量门禁)。
有条件地执行测试
只测试发生变更的部分。使用 dorny/paths-filter@v3 设置输出,然后根据输出控制步骤是否执行——请参阅 references/github-actions-templates.md(有条件执行)。
隔离不稳定测试
将不稳定测试拆分到非阻塞作业中,让它们在 CI 中运行,但不阻止合并:
e2e-stable: # required for merge
steps:
- run: npx playwright test --grep-invert @flaky
e2e-quarantine: # non-blocking
continue-on-error: true
steps:
- run: npx playwright test --grep @flaky
- if: failure()
run: echo "::warning::Quarantined tests failed. Review and fix or remove."
在源代码中为不稳定测试添加标签,以便 grep 将它们分开:
test('sometimes fails due to race condition @flaky', async ({ page }) => {
// runs in CI but doesn't block merges
});
如果隔离的测试连续 10 次运行都通过,请移除 @flaky 标签。若要针对单个不稳定测试实现运行时自愈(选择器恢复、自动重试策略),请使用 test-reliability。
缓存策略
| 层级 | 路径 | 缓存键 |
|---|---|---|
| Node 模块 | (由 setup-node 的 cache: npm 处理) | 自动 |
| Playwright 浏览器 | ~/.cache/ms-playwright | pw-{os}-{hash(package-lock.json)} |
| 构建缓存(Next.js) | .next/cache | nextjs-{os}-{hash(lockfile)}-{hash(src)} |
| 测试夹具 | e2e/fixtures/.cache | test-data-{hash(seed.sql)} |
对第 2–4 层使用 actions/cache@v5;为构建缓存添加 restore-keys,以便进行部分匹配。
使用 OIDC 进行无密钥部署
不要存储长期有效的 DEPLOY_TOKEN。使用 GitHub Actions OIDC 假定云角色,获取短期凭证——无需担心静态密钥泄露或轮换:
deploy:
permissions:
id-token: write # request the OIDC JWT
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
aws-region: eu-central-1
- run: ./deploy.sh production # uses short-lived STS creds, no static secret
IAM 角色的信任策略会将 sub 声明绑定到你的仓库和分支。GCP(google-github-actions/auth)和 Azure(azure/login)也提供等效的 OIDC 流程。
失败时发送 Slack/Teams 通知
使用 slackapi/slack-github-action@v2,并设置 webhook-type: incoming-webhook;通过 if: failure() && github.ref == 'refs/heads/main' 控制,仅在主分支发生失败时发送通知。升级到 @v3 前,请注意 v3 更改了工作流触发的 webhook 的载荷处理方式(不再扁平化/字符串化)——请先对照 v3 文档验证你的载荷。完整示例请参阅 references/github-actions-templates.md(Nightly Full Suite)。
质量门禁
| 门禁 | 触发时机 | 必需检查 | 是否阻塞? |
|---|---|---|---|
| PR 门禁 | PR 创建/更新时 | lint、类型检查、单元测试、覆盖率阈值 | 是 |
| 合并门禁 | 合并到 main 前 | + E2E 冒烟测试套件 | 是 |
| 部署门禁 | 生产环境部署前 | + 完整 E2E、视觉测试、性能预算 | 是 |
| 夜间门禁 | 每天凌晨 2 点定时运行 | 完整测试套件、npm audit、axe 无障碍测试 | 仅告警 |
PR 门禁(3 分钟以内)
在测试运行器的配置中强制执行覆盖率下限,而不要在 bash 中设置。在 jest.config.js(或 vitest.config.ts 的 coverage.thresholds)中:
coverageThreshold: { global: { lines: 80, statements: 80, branches: 70 } }
然后,当覆盖率下降时,jest --coverage 会以非零状态退出,因此作业无需额外脚本便会失败。如果必须在 CI 中读取数值(例如打印数值),请让 Jest 输出 json-summary 并读取文件——没有 coverage-summary CLI:
- run: npm test -- --ci --coverage # exits 1 if below coverageThreshold
- name: Print coverage (optional)
run: |
PCT=$(jq '.total.lines.pct' coverage/coverage-summary.json)
echo "Line coverage: ${PCT}%"
(json-summary reporter 会写入 coverage/coverage-summary.json。对于 nyc/c8 项目,请使用 nyc report --reporter=text-summary。独立的 istanbul CLI 已弃用——不要使用 istanbul report。)
合并门禁(10 分钟以内)
PR 门禁 + E2E 冒烟测试。将其配置为分支保护中的必需状态检查。
部署门禁(15 分钟以内)
需要 [unit-tests, e2e-tests, visual-tests],然后在上文的 OIDC 部署步骤前进行性能预算检查(npx lhci autorun / lhci assert --config=lighthouserc.json)。
夜间门禁(最多 30 分钟)
在所有浏览器中运行完整 E2E、安全扫描、无障碍审计和不稳定测试隔离。将安全和无障碍步骤配置为真正的作业,而不只是文字说明:
- run: npm audit --audit-level=high # fails on high/critical advisories
- run: npx playwright test --grep @a11y # specs that call @axe-core/playwright
其中,带有 @a11y 标签的测试规范使用 @axe-core/playwright:
import AxeBuilder from '@axe-core/playwright';
test('home page has no a11y violations @a11y', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
结果发送到 Slack,不作为阻塞检查。
反模式
1. 每次提交都运行所有测试
每次推送都运行 20 分钟的完整测试套件会拖慢开发进度。使用触发器到测试套件的映射:推送时运行快速测试,在 PR 和合并时运行全面测试。
2. 不存储制品
没有跟踪记录、屏幕截图和日志时,每次 CI 失败都会变成“在本地复现”的循环,白白浪费数小时。在 if: ${{ !cancelled() }} 条件下上传制品。
3. 重试不稳定测试,却不跟踪
retries: 3 会掩盖不稳定性——报告显示通过,但竞争条件仍然存在。将其隔离、跟踪并修复根本原因。
4. 仅在 CI 中失败,却无法在本地复现
如果测试只在 CI 中失败,请记录原因(时区、缺失的环境变量、屏幕分辨率),并添加一个脚本,使用与 CI 中相同的 Playwright 镜像在本地复现 CI 环境——不要固定使用过时的镜像。参见 references/github-actions-templates.md(本地复现)。
5. CI 作业之间共享状态
有些作业会在没有制品或 needs 的情况下读取同级作业中的文件。每个作业都会从干净环境开始;通过 upload-artifact/download-artifact 传递数据。
6. 没有并发控制
同一分支的多次运行会浪费运行器资源。始终使用并发组,并设置 cancel-in-progress: true。
7. 在工作流文件中硬编码密钥
绝不要在 YAML 中放入令牌、密码或密钥。使用仓库密钥(${{ secrets.X }})或 GitLab CI/CD 变量;并优先使用 OIDC 无密钥身份验证,而不是任何长期有效的部署令牌。
8. 忽略作业超时
卡住的测试可能让运行器被占用数小时。为每个作业设置 timeout-minutes,并在 Playwright 配置中设置 actionTimeout/navigationTimeout。
验证
在依赖流水线之前,先证明它能正常工作。先做最小检查:
- 检查工作流语法 —
actionlint .github/workflows/*.yml可在运行时出错之前捕获表达式、needs和 Shell 引号错误。再运行一次yamllint .github/workflows/检查缩进。也要将 actionlint 作为作业运行。 - 在本地试运行作业 —
act -j unit-tests会在容器中运行作业,让你无需推送即可反复调试。 - 确认所需检查项已显示 — 推送到临时分支,创建草稿 PR,并确认预期的检查项(
lint、unit-tests、e2e)都出现,且当覆盖率低于阈值时,覆盖率门禁会失败。 - 验证制品 — 从 Actions 界面下载本次运行的制品(或运行
gh run download <id>),并确认其中包含playwright-report/和跟踪记录。
完成标准
- 存在触发器映射:推送时仅运行 lint 和单元测试;PR 工作流通过
if: github.event_name == 'pull_request'控制 E2E 是否运行(检查 YAML,而不是“某处会运行集成测试”)。 actionlint .github/workflows/*.yml的退出码为 0。- 运行一个非阻塞的隔离作业,执行
--grep @flaky并设置continue-on-error: true;稳定作业执行--grep-invert @flaky,且列在必需检查项中。 - 测试制品(报告、屏幕截图、跟踪记录)在
if: ${{ !cancelled() }}条件下上传,并明确设置retention-days/expire_in。 - PR/测试工作流均设置了并发组,并启用
cancel-in-progress: true。 - 运行器通过
coverageThreshold/thresholds强制执行覆盖率要求(低于下限时作业以非零状态退出)——不使用 CLI 提取coverage-summary。 - 分支保护将
lint、unit-tests和e2e列为必需的状态检查。 - YAML 中不包含长期有效的部署令牌——部署使用 OIDC(
id-token: write+ 云角色),或至少使用密钥存储引用。
相关技能
- playwright-automation — 编写 E2E 测试、Page Object Model,以及本流水线用于分片和设置超时的
playwright.config.ts。 - test-reliability — 对单个不稳定测试进行运行时自愈(选择器恢复、重试策略);修复不稳定测试时请参考该技能,在 CI 中隔离测试时请参考本技能。
- qa-metrics — 将本流水线生成的 JUnit/覆盖率制品转化为仪表板和不稳定趋势。
- release-readiness — 使用这些门禁结果进行人工放行/不放行决策,并执行发布检查清单;本技能构建门禁,该技能依据门禁结果作出决策。
- coverage-analysis — 查找覆盖率缺口并设置本技能的 PR 门禁所强制执行的阈值。
参考文件(位于 references/ 中)
- github-actions-templates.md — 可直接复制粘贴的单元测试、分片 Playwright E2E(+ 报告合并)、完整流水线、夜间(Slack + 审计 + axe)和 PR 质量门工作流,以及条件执行和本地复现代码片段。
- gitlab-ci-template.md — 完整的
.gitlab-ci.yml,包含并行分片、cobertura 覆盖率和 JUnit MR 报告。