mcpField
C
ci-cd-integration

ci-cd-integration

github.com/petrkindlmann云与 DevOps2221.1k 次安装6 天前更新
代码仓库

关于 ci-cd-integration

ci-cd-integration 是用于设计测试流水线的 QA Skill,面向希望提升 CI/CD 测试执行效率的团队。内容涵盖按触发条件选择测试、分片执行、保存产物、隔离不稳定测试及质量门禁,并提供 GitHub Actions 和 GitLab CI 模板。

主要功能

  1. 01按流水线触发条件安排测试
  2. 02在多个 Runner 间分片测试
  3. 03将报告和追踪保存为产物
  4. 04隔离不稳定测试
  5. 05设置覆盖率与合并门禁
  6. 06提供 GitHub Actions 和 GitLab CI 模板

适用场景

  1. 01缩短耗时较长的 CI 测试套件
  2. 02为 Pull Request 发布测试报告
  3. 03避免不稳定测试阻塞合并
  4. 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

详细介绍

每次推送都运行耗时 20 分钟的串行测试套件,会拖慢开发进度;流水线虽然显示通过,却把不稳定测试重试三次,这会掩盖竞态条件,直到问题随代码发布。本技能可生成 CI/CD 流水线:在正确的触发条件下运行合适的测试,将测试分片并分配到多个运行器上,将跟踪记录和报告保存为证据,将不稳定测试隔离而非掩盖,并以实际覆盖率数值作为合并门槛。当问题是如何在流水线中运行测试,而不是如何编写测试时,请使用本技能。

调研问题

先检查 .agents/qa-project-context.md;如果文件存在,则使用其中的信息,并跳过已回答的问题(尤其是 team_maturity 和现有 CI 规范)。然后:

  1. 使用哪种 CI 平台? GitHub Actions、GitLab CI、CircleCI、Jenkins?本技能提供 GitHub Actions 和 GitLab CI 模板。
  2. 需要运行哪些测试类型? 单元测试、集成测试、E2E、视觉测试、性能测试?每种测试的资源和耗时需求都不同。
  3. 目前 CI 耗时多久? 超过 10 分钟就必须并行执行和分片,而非可选项。
  4. 开发者每天推送多少次? 推送频繁的团队需要积极取消并发任务并使用缓存。
  5. 哪些触发条件应运行哪些测试? 并非每次推送都需要运行完整的 E2E 测试套件——编写 YAML 前先将触发条件映射到测试套件。
根据团队成熟度调整

在 .agents/qa-project-context.md 中设置 team_maturity;选择与之匹配的流水线结构:

  • startup —— 一个任务:在 PR 上运行 lint、单元测试和一次 E2E 冒烟测试。优先快速反馈,而非完整覆盖。
  • growing —— 单元测试、集成测试和 E2E 分设任务。并行执行、上传工件、发布结果、隔离不稳定测试。
  • established —— 完整矩阵:分片运行 E2E、多环境晋级门禁、性能与安全扫描、部署门禁检查、由 SLA 保障的流水线。

核心原则

  1. 快速反馈:在合适的时机运行合适的测试。 每次推送都运行单元测试(少于 2 分钟)。PR 上运行 E2E(少于 10 分钟)。合并时和每晚运行完整测试套件。下方的触发条件到测试套件映射表就是约定。
  2. 优先并行:将测试分片并分配给多个工作器。 串行运行需 20 分钟的测试套件,分成 4 个分片后只需 5 分钟。运行器成本总是值得的。
  3. 工件就是证据。 每次运行都保存跟踪记录、截图、覆盖率和 HTML 报告。没有工件,CI 失败就会陷入无法调试的“在本地复现”循环。
  4. 不稳定测试应隔离,而非重试。 重试会掩盖问题——测试重试后通过,报告显示绿色,但竞态条件依然存在。将不稳定测试移到不阻塞流程的任务中,跟踪它们并修复根本原因。
  5. 越接近生产环境,质量门槛越严格。 明确每个阶段必须通过哪些检查;PR 门禁应快速且成本低,部署门禁则应全面。
  6. 从配置中读取阈值,而非从 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@v6v5+ 仅在设置了 packageManager 时自动缓存;使用 cache: npm 明确指定
actions/cache@v5新缓存服务 v2 后端
actions/upload-artifact@v7v7 可以上传未压缩的文件(archive: false)
actions/download-artifact@v7与相同主版本的 upload-artifact 配对使用
dorny/test-reporter@v3v3 需要 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-playwrightpw-{os}-{hash(package-lock.json)}
构建缓存(Next.js).next/cachenextjs-{os}-{hash(lockfile)}-{hash(src)}
测试夹具e2e/fixtures/.cachetest-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。


验证

在依赖流水线之前,先证明它能正常工作。先做最小检查:

  1. 检查工作流语法 — actionlint .github/workflows/*.yml 可在运行时出错之前捕获表达式、needs 和 Shell 引号错误。再运行一次 yamllint .github/workflows/ 检查缩进。也要将 actionlint 作为作业运行。
  2. 在本地试运行作业 — act -j unit-tests 会在容器中运行作业,让你无需推送即可反复调试。
  3. 确认所需检查项已显示 — 推送到临时分支,创建草稿 PR,并确认预期的检查项(lint、unit-tests、e2e)都出现,且当覆盖率低于阈值时,覆盖率门禁会失败。
  4. 验证制品 — 从 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 报告。

评论

支持 Markdown