ci-cd-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese<objective>
A 20-minute serial suite on every push destroys developer velocity; a green pipeline that retries flaky tests three times hides the race condition until it ships. This skill produces CI/CD pipelines that run the right tests at the right trigger, shard them across runners, store traces and reports as evidence, quarantine flaky tests instead of masking them, and gate merges on real coverage numbers. Use this skill when the question is about running tests in a pipeline, not writing them.
</objective>
<objective>
每次推送都运行20分钟的串行测试套件会严重拖慢开发效率;自动重试不稳定测试三次的「绿色管道」会掩盖竞态条件,直到问题随版本上线。本技能可构建CI/CD管道,在正确的触发时机运行合适的测试,将测试分片到多个运行器,存储跟踪信息和报告作为证据,隔离不稳定测试而非掩盖问题,并基于真实覆盖率数据管控代码合并。当问题涉及在管道中运行测试(而非编写测试)时,使用本技能。
</objective>
Discovery Questions
探索问题
Check first — if it exists, use it and skip anything already answered there (especially and existing CI conventions). Then:
.agents/qa-project-context.mdteam_maturity- Which CI platform? GitHub Actions, GitLab CI, CircleCI, Jenkins? This skill ships templates for GitHub Actions and GitLab CI.
- What test types need to run? Unit, integration, E2E, visual, performance? Each has different resource and timing needs.
- What is the current CI duration? Over 10 minutes means parallelism and sharding are mandatory, not optional.
- How many developers push per day? High-frequency teams need aggressive concurrency cancellation and caching.
- What triggers should run which tests? Not every push needs a full E2E suite — map triggers to suites before writing YAML.
首先查看——如果该文件存在,使用其中内容并跳过已回答的问题(尤其是和现有CI约定)。然后询问:
.agents/qa-project-context.mdteam_maturity- 使用哪种CI平台? GitHub Actions、GitLab CI、CircleCI还是Jenkins?本技能提供GitHub Actions和GitLab CI的模板。
- 需要运行哪些类型的测试? 单元测试、集成测试、E2E测试、可视化测试、性能测试?每种测试的资源和时间需求不同。
- 当前CI执行时长是多少? 超过10分钟意味着必须采用并行化和分片,而非可选方案。
- 每天有多少开发者推送代码? 高频推送的团队需要设置严格的并发取消和缓存策略。
- 哪些触发事件应运行哪些测试? 并非每次推送都需要完整的E2E套件——在编写YAML前先映射触发事件与测试套件的对应关系。
Calibrate to team maturity
根据团队成熟度调整
Set in ; pick the matching pipeline shape:
team_maturity.agents/qa-project-context.md- startup — one job: lint + unit + one E2E smoke on PR. Fast feedback over completeness.
- growing — separate jobs for unit, integration, E2E. Parallelization, artifact uploads, result publishing, flaky quarantine.
- established — full matrix: sharded E2E, multi-environment promotion gates, perf and security scans, deploy-gated checks, SLA-backed pipelines.
在中设置,选择匹配的管道架构:
.agents/qa-project-context.mdteam_maturity- 初创团队 —— 单任务:PR中运行代码检查+单元测试+一个E2E冒烟测试。优先保证快速反馈而非测试完整性。
- 成长中团队 —— 单元测试、集成测试、E2E测试分设独立任务。支持并行化、工件上传、结果发布、不稳定测试隔离。
- 成熟团队 —— 完整矩阵架构:分片E2E测试、多环境发布门禁、性能与安全扫描、部署前置检查、符合SLA的管道。
Core Principles
核心原则
- Fast feedback: right tests at the right time. Unit tests on every push (under 2 min). E2E on PRs (under 10 min). Full suite on merge and nightly. The trigger-to-suite map below is the contract.
- Parallel first: shard tests across workers. A 20-minute serial suite becomes 5 minutes across 4 shards. Always worth the runner cost.
- Artifacts are evidence. Every run stores traces, screenshots, coverage, and HTML reports. Without artifacts, a CI failure is an undebuggable "reproduce locally" cycle.
- Flaky tests need quarantine, not retries. Retrying hides the problem — the test passes on retry, the report is green, the race condition persists. Move flaky tests to a non-blocking job, track them, fix the root cause.
- Quality gates get stricter toward production. Define what must pass at each stage; PR gate is fast and cheap, deploy gate is comprehensive.
- Read thresholds from config, not from bash. Let the test runner enforce coverage via its own /
coverageThresholdand exit non-zero. Scraping percentages out of stdout with regex is fragile across runner versions.thresholds
- 快速反馈:在正确的时机运行正确的测试。每次推送运行单元测试(耗时≤2分钟)。PR中运行E2E测试(耗时≤10分钟)。合并到主分支和夜间定时任务运行完整套件。下方的触发-套件映射为标准约定。
- 优先并行:将测试分片到多个运行器。20分钟的串行套件在4个分片下可缩短至5分钟。即使增加运行器成本也值得。
- 工件即证据。每次运行都存储跟踪信息、截图、覆盖率报告和HTML报告。没有工件的话,CI失败就会变成无法调试的「本地复现」循环。
- 不稳定测试需要隔离而非重试。重试会掩盖问题——测试重试后通过,报告显示绿色,但竞态条件依然存在。将不稳定测试移至非阻塞任务,跟踪并修复根本原因。
- 越接近生产环境,质量门禁越严格。定义每个阶段必须通过的检查;PR门禁快速且低成本,部署门禁全面且严格。
- 从配置读取阈值,而非从bash脚本。让测试运行器通过自身的/
coverageThreshold配置强制覆盖率要求,并在不满足时返回非零退出码。通过正则表达式从标准输出中提取百分比的方式在不同版本的运行器中容易失效。thresholds
Pipeline Architecture
管道架构
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+类型检查(30s) → 单元测试(1-2m)
PR创建: + 集成测试(2-3m) → 分片E2E测试(5-8m) → 合并报告
合并到主分支: 完整E2E测试 ∥ 可视化测试 ∥ 性能预算检查 → 部署(OIDC)
夜间定时任务(cron): 完整套件 + npm审计 + axe无障碍测试 + 不稳定测试隔离What runs when
触发事件与测试对应关系
| Trigger | Tests | Max duration |
|---|---|---|
| Push to branch | lint, type-check, unit | 2 min |
| PR opened/updated | + integration, E2E smoke | 10 min |
| Merge to main | + full E2E, visual, perf budget | 15 min |
| Nightly schedule | full suite, security, a11y, flaky quarantine | 30 min |
| Release tag | full suite, smoke against staging | 20 min |
| 触发事件 | 测试内容 | 最长耗时 |
|---|---|---|
| 分支推送 | 代码检查、类型检查、单元测试 | 2分钟 |
| PR创建/更新 | + 集成测试、E2E冒烟测试 | 10分钟 |
| 合并到主分支 | + 完整E2E测试、可视化测试、性能预算检查 | 15分钟 |
| 夜间定时任务 | 完整套件、安全测试、无障碍测试、不稳定测试隔离 | 30分钟 |
| 发布标签 | 完整套件、 staging环境冒烟测试 | 20分钟 |
GitHub Actions
GitHub Actions
For complete copy-paste workflow files (unit, sharded Playwright E2E, full pipeline, nightly, PR gate), see .
references/github-actions-templates.md完整的可复用工作流文件(单元测试、分片Playwright E2E测试、完整管道、夜间任务、PR门禁)请查看。
references/github-actions-templates.mdAction versions (June 2026)
动作版本(2026年6月)
Pin to the current major and let Dependabot bump them. The family runs on the Node 24 runner; Node 20 is deprecated on GH-hosted runners.
actions/*| Action | Current major | Notes |
|---|---|---|
| | |
| | v5+ auto-caches only when |
| | new cache service v2 backend |
| | v7 can upload unzipped ( |
| | pair with upload-artifact major |
| | v3 requires Node 24 runner; reporter keys unchanged |
| | |
| | |
| | floating major; see notification note before adopting v3 |
For supply-chain-sensitive pipelines, pin third-party actions (dorny, marocchino, slackapi, knapsack) to a full-length commit SHA with a version comment, and let Dependabot update the SHA: . First-party are lower risk; tags are acceptable there.
uses: dorny/test-reporter@<40-char-sha> # v3.0.0actions/*固定使用当前大版本,让Dependabot自动更新。系列动作运行在Node 24运行器上;Node 20在GitHub托管运行器中已被弃用。
actions/*| 动作 | 当前大版本 | 说明 |
|---|---|---|
| | |
| | v5+仅在设置 |
| | 采用新的缓存服务v2后端 |
| | v7支持上传未压缩文件( |
| | 与upload-artifact大版本保持一致 |
| | v3需要Node 24运行器;报告器密钥未变更 |
| | |
| | |
| | 使用浮动大版本;升级到v3前请查看通知说明 |
对于对供应链安全敏感的管道,将第三方动作(dorny、marocchino、slackapi、knapsack)固定到完整的提交SHA并添加版本注释,让Dependabot自动更新SHA:。官方动作风险较低,使用标签即可。
uses: dorny/test-reporter@<40位SHA> # v3.0.0actions/*Key concepts
核心概念
Concurrency groups cancel wasted runs when a branch gets multiple pushes:
yaml
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: trueMatrix sharding across runners:
yaml
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4Caching browsers so they aren't re-downloaded every run:
yaml
- 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 chromiumArtifacts for reports and traces, and merging sharded reports into one HTML report — see (E2E workflow). The merge job uses with then .
references/github-actions-templates.mdactions/download-artifact@v7pattern: test-results-*npx playwright merge-reports --reporter=html并发组可在分支多次推送时取消冗余运行:
yaml
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true矩阵分片跨运行器执行:
yaml
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4缓存浏览器避免每次运行重新下载:
yaml
- 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报告——请查看(E2E工作流)。合并任务使用并设置,然后执行。
references/github-actions-templates.mdactions/download-artifact@v7pattern: test-results-*npx playwright merge-reports --reporter=htmlSmarter sharding at scale
大规模场景下的智能分片
Past 10–15 shards, naïve hash-based splitting wastes runner time on uneven shards. Use a timing-aware balancer:
- — timing-data based, supports Playwright/Jest/Cypress/RSpec; distributes by historical duration.
knapsack-pro - CloudBees Smart Tests (formerly Launchable) — ML prioritization + Test Impact Analysis; runs only the tests likely to fail for the diff.
- Datadog Test Optimization — TIA + flake management; shard-balancing by historical time.
- Trunk Flaky Tests — flake-aware quarantine + retry budgeting.
Before reaching for a paid balancer: Playwright's already distributes by file and balances on duration from prior runs. To inspect or feed custom timing data, dump it yourself — . For Jest, surfaces the slowest specs so you can split or fix them.
--shardnpx playwright test --reporter=json | jq '[.suites[].specs[] | {file: .file, duration: .tests[].results[].duration}]'jest-slow-test-reporterFor self-hosted runners on Kubernetes, use Actions Runner Controller ( / ) — Helm-installed, auto-scales runner pods per workflow. Replaces the deprecated CRD.
arc-runner-setgha-runner-scale-setrunner-deployment当分片数量超过10-15个时,简单的哈希分片会因分片负载不均浪费运行器时间。使用基于时间的平衡工具:
- —— 基于时间数据,支持Playwright/Jest/Cypress/RSpec;根据历史执行时长分配测试。
knapsack-pro - CloudBees Smart Tests(原Launchable)—— ML优先级排序+测试影响分析;仅运行可能因代码变更失败的测试。
- Datadog Test Optimization —— 测试影响分析+不稳定测试管理;根据历史时间平衡分片。
- Trunk Flaky Tests —— 感知不稳定测试的隔离+重试预算管理。
在使用付费平衡工具前:Playwright的已支持按文件分配,并根据之前的运行时长平衡负载。如需查看或自定义时间数据,可自行导出——。对于Jest,可显示最慢的测试用例,方便拆分或优化。
--shardnpx playwright test --reporter=json | jq '[.suites[].specs[] | {file: .file, duration: .tests[].results[].duration}]'jest-slow-test-reporter对于Kubernetes上的自托管运行器,使用Actions Runner Controller( / )——通过Helm安装,可根据工作流自动扩缩容运行器Pod。替代已弃用的 CRD。
arc-runner-setgha-runner-scale-setrunner-deploymentRequired status checks
必需状态检查
Protect main in Settings → Branches → Branch protection rules: enable "Require status checks to pass before merging," add , , and (all shards) as required checks, and enable "Require branches to be up to date."
lintunit-testse2e在设置→分支→分支保护规则中保护主分支:启用「合并前需要状态检查通过」,添加、和(所有分片)作为必需检查,并启用「要求分支保持最新」。
lintunit-testse2eGitLab CI
GitLab CI
For the full pipeline, see . Key points:
references/gitlab-ci-template.md- Stages ;
[validate, test, e2e, deploy]for lint/unit,node:22-alpinefor E2E (keep this pinned to your installedmcr.microsoft.com/playwright:v1.60.0-nobleminor).@playwright/test - Parallel sharding: exposes
parallel: 4/CI_NODE_INDEX; runCI_NODE_TOTAL.npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL - Coverage: emit a cobertura artifact and a
coverage_reportreport; GitLab reads the percentage and test results from those. The legacyjunitstdout regex is a fragile fallback across Jest versions — prefer the cobertura report.coverage:
完整管道请查看。核心要点:
references/gitlab-ci-template.md- 阶段为;lint/单元测试使用
[validate, test, e2e, deploy]镜像,E2E测试使用node:22-alpine镜像(请固定到与已安装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报告;GitLab会从中读取覆盖率百分比和测试结果。不同Jest版本中,通过标准输出正则表达式提取覆盖率的传统junit配置容易失效——优先使用cobertura报告。coverage:
Advanced Patterns
高级模式
Test result publishing to PR comments
测试结果发布到PR评论
yaml
- 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 reportFor the sticky coverage PR comment (), see (PR Quality Gate).
marocchino/sticky-pull-request-comment@v3references/github-actions-templates.mdyaml
- name: Publish test results
uses: dorny/test-reporter@v3
if: ${{ !cancelled() }}
with:
name: Test Results
path: test-results/junit.xml
reporter: jest-junit # Playwright JUnit报告使用java-junit固定覆盖率PR评论()示例请查看(PR质量门禁)。
marocchino/sticky-pull-request-comment@v3references/github-actions-templates.mdConditional test execution
条件测试执行
Only test what changed. Use to set outputs, then gate steps on them — see (Conditional execution).
dorny/paths-filter@v3references/github-actions-templates.md仅测试变更的代码。使用设置输出变量,然后根据变量管控步骤执行——示例请查看(条件执行)。
dorny/paths-filter@v3references/github-actions-templates.mdFlaky test quarantine
不稳定测试隔离
Separate flaky tests into a non-blocking job so they run in CI but don't block merges:
yaml
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."Tag flaky tests at the source so the grep splits them:
typescript
test('sometimes fails due to race condition @flaky', async ({ page }) => {
// runs in CI but doesn't block merges
});If a quarantined test passes 10 consecutive runs, remove the tag. For runtime self-healing of a single flaky test (selector recovery, auto-retry policy), use .
@flakytest-reliability将不稳定测试分离到非阻塞任务,使其在CI中运行但不阻止代码合并:
yaml
e2e-stable: # 合并必需任务
steps:
- run: npx playwright test --grep-invert @flaky
e2e-quarantine: # 非阻塞任务
continue-on-error: true
steps:
- run: npx playwright test --grep @flaky
- if: failure()
run: echo "::warning::隔离测试失败,请检查修复或移除。"在测试源代码中标记不稳定测试,以便通过grep拆分:
typescript
test('有时因竞态条件失败 @flaky', async ({ page }) => {
// 在CI中运行但不阻止合并
});如果隔离测试连续10次运行通过,移除标签。如需实现单不稳定测试的运行时自修复(选择器恢复、自动重试策略),请使用技能。
@flakytest-reliabilityCache strategies
缓存策略
| Layer | Path | Cache key |
|---|---|---|
| Node modules | (handled by | automatic |
| Playwright browsers | | |
| Build cache (Next.js) | | |
| Test fixtures | | |
Use for layers 2–4; add on build caches for partial matches.
actions/cache@v5restore-keys| 层级 | 路径 | 缓存键 |
|---|---|---|
| Node模块 | (由 | 自动生成 |
| Playwright浏览器 | | |
| 构建缓存(Next.js) | | |
| 测试夹具 | | |
使用处理第2-4层级;构建缓存添加以支持部分匹配恢复。
actions/cache@v5restore-keysOIDC keyless deploy
OIDC无密钥部署
Don't store a long-lived . Use GitHub Actions OIDC to assume a cloud role for short-lived credentials — nothing static to leak or rotate:
DEPLOY_TOKENyaml
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 secretThe IAM role's trust policy pins the claim to your repo and branch. GCP () and Azure () have equivalent OIDC flows.
subgoogle-github-actions/authazure/login不要存储长期有效的。使用GitHub Actions OIDC获取云角色的短期凭证——无需静态密钥,避免泄露或轮换成本:
DEPLOY_TOKENyaml
deploy:
permissions:
id-token: write # 请求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 # 使用短期STS凭证,无静态密钥IAM角色的信任策略需将声明绑定到你的仓库和分支。GCP()和Azure()有类似的OIDC流程。
subgoogle-github-actions/authazure/loginSlack/Teams notification on failure
失败时发送Slack/Teams通知
Use with , gated on so only main-branch failures notify. Before moving to , note v3 changed payload handling for workflow-trigger webhooks (no longer flattened/stringified) — verify your payload against the v3 docs first. Full example in (Nightly Full Suite).
slackapi/slack-github-action@v2webhook-type: incoming-webhookif: failure() && github.ref == 'refs/heads/main'@v3references/github-actions-templates.md使用并设置,仅在主分支失败时触发()。升级到前请注意,v3变更了工作流触发webhook的负载处理方式(不再扁平化/字符串化)——请先对照v3文档验证负载格式。完整示例请查看(夜间完整套件)。
slackapi/slack-github-action@v2webhook-type: incoming-webhookif: failure() && github.ref == 'refs/heads/main'@v3references/github-actions-templates.mdQuality Gates
质量门禁
| Gate | When | Required checks | Blocking? |
|---|---|---|---|
| PR Gate | PR opened/updated | lint, type-check, unit, coverage threshold | Yes |
| Merge Gate | Before merge to main | + E2E smoke suite | Yes |
| Deploy Gate | Before production deploy | + full E2E, visual, perf budget | Yes |
| Nightly Gate | Scheduled 2am daily | full suite, npm audit, axe a11y | Alert only |
| 门禁 | 触发时机 | 必需检查 | 是否阻塞 |
|---|---|---|---|
| PR门禁 | PR创建/更新 | 代码检查、类型检查、单元测试、覆盖率阈值 | 是 |
| 合并门禁 | 合并到主分支前 | + E2E冒烟测试 | 是 |
| 部署门禁 | 生产环境部署前 | + 完整E2E测试、可视化测试、性能预算检查 | 是 |
| 夜间门禁 | 每日凌晨2点定时任务 | 完整套件、npm审计、axe无障碍测试 | 仅告警 |
PR Gate (under 3 minutes)
PR门禁(耗时≤3分钟)
Enforce the coverage floor in the test runner's config, not in bash. In (or ):
jest.config.jsvitest.config.tscoverage.thresholdsjavascript
coverageThreshold: { global: { lines: 80, statements: 80, branches: 70 } }Then exits non-zero when coverage drops, so the job fails with no extra script. If you must read the number in CI (e.g. to print it), have Jest emit and read the file — there is no CLI:
jest --coveragejson-summarycoverage-summaryyaml
- 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}%"( reporter writes . For nyc/c8 projects, . The standalone CLI is deprecated — don't use .)
json-summarycoverage/coverage-summary.jsonnyc report --reporter=text-summaryistanbulistanbul report在测试运行器的配置中强制覆盖率下限,而非通过bash脚本。在(或的)中设置:
jest.config.jsvitest.config.tscoverage.thresholdsjavascript
coverageThreshold: { global: { lines: 80, statements: 80, branches: 70 } }然后执行,当覆盖率低于阈值时会返回非零退出码,任务自动失败,无需额外脚本。如果必须在CI中读取覆盖率数值(例如打印),让Jest生成报告并读取文件——没有命令行工具:
jest --coveragejson-summarycoverage-summaryyaml
- run: npm test -- --ci --coverage # 覆盖率低于阈值时退出码为1
- name: 打印覆盖率(可选)
run: |
PCT=$(jq '.total.lines.pct' coverage/coverage-summary.json)
echo "行覆盖率: ${PCT}%"(报告器会生成。对于nyc/c8项目,执行。独立的命令行工具已弃用——请勿使用。)
json-summarycoverage/coverage-summary.jsonnyc report --reporter=text-summaryistanbulistanbul reportMerge Gate (under 10 minutes)
合并门禁(耗时≤10分钟)
PR Gate + E2E smoke. Configure as required status checks in branch protection.
PR门禁 + E2E冒烟测试。在分支保护中配置为必需状态检查。
Deploy Gate (under 15 minutes)
部署门禁(耗时≤15分钟)
Needs , then a perf budget check ( / ) before the OIDC deploy step above.
[unit-tests, e2e-tests, visual-tests]npx lhci autorunlhci assert --config=lighthouserc.json需要通过,然后在上述OIDC部署步骤前执行性能预算检查( / )。
[unit-tests, e2e-tests, visual-tests]npx lhci autorunlhci assert --config=lighthouserc.jsonNightly Gate (up to 30 minutes)
夜间门禁(耗时≤30分钟)
Full E2E across all browsers, security scan, a11y audit, flaky quarantine. Wire the security and a11y steps as real jobs, not just prose:
yaml
- run: npm audit --audit-level=high # fails on high/critical advisories
- run: npx playwright test --grep @a11y # specs that call @axe-core/playwrightWhere the -tagged specs use :
@a11y@axe-core/playwrighttypescript
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([]);
});Results go to Slack, not as blocking checks.
全浏览器完整E2E测试、安全扫描、无障碍审计、不稳定测试隔离。将安全和无障碍步骤配置为真实任务,而非仅文档说明:
yaml
- run: npm audit --audit-level=high # 高/严重漏洞时任务失败
- run: npx playwright test --grep @a11y # 调用@axe-core/playwright的测试用例其中标记的测试用例使用:
@a11y@axe-core/playwrighttypescript
import AxeBuilder from '@axe-core/playwright';
test('首页无无障碍违规 @a11y', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});结果发送到Slack,不设置为阻塞检查。
Anti-Patterns
反模式
1. Running all tests on every commit
1. 每次提交运行所有测试
A 20-minute full suite on every push destroys velocity. Use the trigger-to-suite map: fast tests on push, comprehensive on PR and merge.
每次推送都运行20分钟的完整套件会严重拖慢开发效率。使用触发-套件映射:推送时运行快速测试,PR和合并时运行全面测试。
2. No artifact storage
2. 不存储工件
Without traces, screenshots, and logs, every CI failure becomes a "reproduce locally" cycle that wastes hours. Upload artifacts on .
if: ${{ !cancelled() }}没有跟踪信息、截图和日志的话,每次CI失败都会变成浪费时间的「本地复现」循环。在条件下上传工件。
if: ${{ !cancelled() }}3. Retrying flaky tests without tracking them
3. 重试不稳定测试但不跟踪
retries: 3retries: 34. CI-only failures without local reproduction
4. CI专属失败无法本地复现
If a test only fails in CI, document why (timezone, missing env var, screen resolution) and add a script that replicates CI locally with the same Playwright image you run in CI — don't pin a stale image. See (Local repro).
references/github-actions-templates.md如果测试仅在CI中失败,记录原因(时区、缺失环境变量、屏幕分辨率)并添加脚本,使用与CI中相同的Playwright镜像在本地复现——不要固定过时的镜像。示例请查看(本地复现)。
references/github-actions-templates.md5. Shared state between CI jobs
5. CI任务间共享状态
Jobs that read files from sibling jobs without artifacts or . Each job starts fresh; pass data via /.
needsupload-artifactdownload-artifact任务通过读取兄弟任务的文件(而非工件或)共享状态。每个任务都从全新环境开始;通过/传递数据。
needsupload-artifactdownload-artifact6. No concurrency controls
6. 无并发控制
Multiple runs for the same branch waste runners. Always use a concurrency group with .
cancel-in-progress: true同一分支的多次运行会浪费运行器资源。始终设置并发组并启用。
cancel-in-progress: true7. Hardcoded secrets in workflow files
7. 工作流文件中硬编码密钥
Never put tokens, passwords, or keys in YAML. Use repo secrets () or GitLab CI/CD variables — and prefer OIDC keyless auth over any long-lived deploy token.
${{ secrets.X }}永远不要在YAML中存储令牌、密码或密钥。使用仓库密钥()或GitLab CI/CD变量——并且优先使用OIDC无密钥认证,而非任何长期有效的部署令牌。
${{ secrets.X }}8. Ignoring job timeouts
8. 忽略任务超时
A stuck test can hold a runner for hours. Set on every job and / in the Playwright config.
timeout-minutesactionTimeoutnavigationTimeout卡住的测试会占用运行器数小时。为每个任务设置,并在Playwright配置中设置/。
timeout-minutesactionTimeoutnavigationTimeoutVerification
验证
Prove the pipeline before relying on it. Smallest check first:
- Lint the workflow syntax — catches expression,
actionlint .github/workflows/*.yml, and shell-quoting errors before they fail at runtime. Add aneedspass for indentation. Run actionlint as a job too.yamllint .github/workflows/ - Dry-run a job locally — runs the job in a container so you can iterate without pushing.
act -j unit-tests - Confirm required checks appear — push to a throwaway branch, open a draft PR, and verify the expected check runs (,
lint,unit-tests) show up and that the coverage gate fails when you drop coverage below the threshold.e2e - Verify artifacts — download the run's artifacts from the Actions UI (or ) and confirm
gh run download <id>and traces are present.playwright-report/
在依赖管道前先验证其有效性。从最小检查开始:
- 检查工作流语法 —— 可在运行前捕获表达式、
actionlint .github/workflows/*.yml和shell引号错误。添加needs检查缩进。也可将actionlint作为CI任务运行。yamllint .github/workflows/ - 本地试运行任务 —— 可在容器中运行任务,无需推送代码即可迭代调试。
act -j unit-tests - 确认必需检查显示 —— 推送代码到临时分支,创建草稿PR,验证预期的检查(、
lint、unit-tests)是否运行,且当覆盖率低于阈值时门禁是否失败。e2e - 验证工件 —— 从Actions UI下载运行的工件(或使用),确认
gh run download <id>和跟踪信息存在。playwright-report/
Done When
完成标准
- A trigger map exists: push runs lint+unit only; the PR workflow gates E2E behind (verify the YAML, not "integration runs somewhere").
if: github.event_name == 'pull_request' - exits 0.
actionlint .github/workflows/*.yml - A non-blocking quarantine job runs with
--grep @flaky; the stable job runscontinue-on-error: trueand is in the required checks list.--grep-invert @flaky - Test artifacts (reports, screenshots, traces) upload on with an explicit
if: ${{ !cancelled() }}/retention-days.expire_in - Concurrency groups with are set on the PR/test workflows.
cancel-in-progress: true - Coverage is enforced by the runner's /
coverageThreshold(job exits non-zero below the floor) — nothresholdsCLI scrape.coverage-summary - Branch protection lists ,
lint, andunit-testsas required status checks.e2e - No long-lived deploy token in YAML — deploy uses OIDC (+ cloud role) or, at minimum, a secrets-store reference.
id-token: write
- 存在触发映射:分支推送仅运行lint+单元测试;PR工作流通过管控E2E测试(验证YAML配置,而非仅确认「集成测试在某处运行」)。
if: github.event_name == 'pull_request' - 返回0。
actionlint .github/workflows/*.yml - 存在非阻塞隔离任务,执行并设置
--grep @flaky;稳定任务执行continue-on-error: true并加入必需检查列表。--grep-invert @flaky - 测试工件(报告、截图、跟踪信息)在条件下上传,并设置明确的
if: ${{ !cancelled() }}/retention-days。expire_in - PR/测试工作流设置了带有的并发组。
cancel-in-progress: true - 覆盖率通过运行器的/
coverageThreshold强制管控(低于下限任务返回非零退出码)——无thresholds命令行提取逻辑。coverage-summary - 分支保护列表中、
lint和unit-tests为必需状态检查。e2e - YAML中无长期有效部署令牌——部署使用OIDC(+ 云角色),或至少使用密钥存储引用。
id-token: write
Related Skills
相关技能
- playwright-automation — writing the E2E tests, Page Object Model, and the whose sharding/timeouts this pipeline drives.
playwright.config.ts - test-reliability — runtime self-healing of one flaky test (selector recovery, retry policy); go there to fix a flaky test, come here to quarantine it in CI.
- qa-metrics — turning the JUnit/coverage artifacts this pipeline produces into dashboards and flakiness trends.
- release-readiness — the human go/no-go decision and release checklist that consumes these gate results; this skill builds the gates, that one decides on them.
- coverage-analysis — finding the coverage gaps and setting the threshold this skill's PR gate enforces.
- playwright-automation —— 编写E2E测试、页面对象模型,以及配置分片/超时的,本管道基于这些配置运行。
playwright.config.ts - test-reliability —— 单不稳定测试的运行时自修复(选择器恢复、重试策略);修复不稳定测试请使用该技能,在CI中隔离不稳定测试请使用本技能。
- qa-metrics —— 将本管道生成的JUnit/覆盖率工件转换为仪表板和不稳定测试趋势报告。
- release-readiness —— 基于门禁结果做出人工发布决策和发布清单;本技能构建门禁,该技能基于门禁做出决策。
- coverage-analysis —— 发现覆盖率缺口并设置本技能PR门禁强制执行的阈值。
Reference Files (in references/
)
references/参考文件(位于references/
)
references/- github-actions-templates.md — copy-paste unit, sharded Playwright E2E (+ report merge), full pipeline, nightly (Slack + audit + axe), and PR quality-gate workflows, plus conditional execution and local-repro snippets.
- gitlab-ci-template.md — full with parallel sharding, cobertura coverage, and JUnit MR reporting.
.gitlab-ci.yml
- github-actions-templates.md —— 可直接复用的单元测试、分片Playwright E2E测试(+报告合并)、完整管道、夜间任务(Slack+审计+axe)、PR质量门禁工作流,以及条件执行和本地复现代码片段。
- gitlab-ci-template.md —— 完整的,包含并行分片、cobertura覆盖率、JUnit MR报告。
.gitlab-ci.yml