18 KiB
CI:GitHub Actions
使用时机:在拉取请求、合并到主分支或按计划自动运行 Playwright 测试。GitHub Actions 是 Playwright 项目中最常用的 CI。
快速参考
# CI 的关键 CLI 标志
npx playwright install --with-deps # 安装浏览器 + 操作系统依赖
npx playwright test --shard=1/4 # 运行 4 个分片中的第 1 个
npx playwright test --reporter=github # 在 PR 上标注失败信息
npx playwright merge-reports ./blob-report # 合并分片报告
模式
模式 1:生产就绪工作流(可直接复制的起步模板)
使用时机:任何使用 GitHub Actions 的项目。这是一个完整且经过实战检验的工作流。
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
# 取消同一 PR/分支上的进行中运行
concurrency:
group: playwright-${{ github.ref }}
cancel-in-progress: true
env:
CI: true
jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
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
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Run Playwright tests
run: npx playwright test
- name: Upload HTML report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 14
- name: Upload test traces
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-traces
path: test-results/
retention-days: 7
模式 2:使用矩阵策略的分片执行
使用时机:测试套件耗时超过 10 分钟。分散到多个并行运行器以缩短实际耗时。 避免时机:测试套件在 5 分钟以内——分片开销(检出、安装、合并)会抵消其带来的好处。
# .github/workflows/playwright-sharded.yml
name: Playwright Tests (Sharded)
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: playwright-${{ github.ref }}
cancel-in-progress: true
env:
CI: true
jobs:
test:
timeout-minutes: 20
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
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
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Run Playwright tests (shard ${{ matrix.shard }})
run: npx playwright test --shard=${{ matrix.shard }}
- name: Upload blob report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: blob-report-${{ strategy.job-index }}
path: blob-report/
retention-days: 1
merge-reports:
if: ${{ !cancelled() }}
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Download all blob reports
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge reports
run: npx playwright merge-reports --reporter=html ./all-blob-reports
- name: Upload merged HTML report
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
分片配置——添加 blob reporter 以便合并分片输出:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { open: "on-failure" }]],
})
模式 3:可复用工作流
使用时机:多个仓库或多个工作流文件需要相同的 Playwright 配置。 避免时机:只有一个工作流的单个仓库。
# .github/workflows/playwright-reusable.yml
name: Playwright Reusable
on:
workflow_call:
inputs:
node-version:
type: string
default: "20"
test-command:
type: string
default: "npx playwright test"
shard-total:
type: number
default: 1
secrets:
BASE_URL:
required: false
TEST_PASSWORD:
required: false
jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: ${{ fromJson(format('[{0}]', join(fromJson(format('[{0}]', inputs.shard-total == 1 && '"1/1"' || '"1/4","2/4","3/4","4/4"')), ','))) }}
env:
CI: true
BASE_URL: ${{ secrets.BASE_URL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: "npm"
- run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
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
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Run tests
run: ${{ inputs.test-command }} --shard=${{ matrix.shard }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ strategy.job-index }}
path: playwright-report/
retention-days: 14
调用可复用工作流:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
e2e:
uses: ./.github/workflows/playwright-reusable.yml
with:
node-version: "20"
shard-total: 4
secrets:
BASE_URL: ${{ secrets.STAGING_URL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
模式 4:在容器中运行
使用时机:你需要一个与本地 Docker 运行一致的可重现环境,或者运行器的操作系统依赖引起问题。
避免时机:标准的 ubuntu-latest 配合 --with-deps 已经能正常工作(这是常见情况)。
# .github/workflows/playwright-container.yml
name: Playwright (Container)
on:
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.52.0-noble
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
# 无需安装浏览器——它们已在容器镜像中
- name: Run Playwright tests
run: npx playwright test
env:
HOME: /root # 在容器中以 root 身份运行时需要
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 14
模式 5:环境密钥与部署目标
使用时机:测试针对需要身份验证凭据的预发/生产环境运行。 避免时机:测试只针对本地启动的开发服务器运行。
# .github/workflows/playwright-staging.yml
name: Playwright (Staging)
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
environment: staging # 包含保护规则的 GitHub Environment
env:
CI: true
BASE_URL: ${{ vars.STAGING_URL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
API_KEY: ${{ secrets.API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
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
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Run tests against staging
run: npx playwright test --grep @smoke
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: staging-report
path: playwright-report/
retention-days: 14
模式 6:定时运行(夜间回归)
使用时机:完整回归套件对每个 PR 来说太慢。改为每晚针对主分支运行一次。 避免时机:测试套件在 15 分钟内跑完,且可以在每个 PR 上运行。
# .github/workflows/playwright-nightly.yml
name: Nightly Regression
on:
schedule:
- cron: "0 3 * * 1-5" # UTC 时间凌晨 3 点,周一至周五
workflow_dispatch: # 允许手动触发
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
env:
CI: true
BASE_URL: ${{ vars.STAGING_URL }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run full regression suite
run: npx playwright test --grep @regression
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: nightly-report-${{ github.run_number }}
path: playwright-report/
retention-days: 30
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"text": "Nightly Playwright regression failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
决策指南
| 场景 | 做法 | 原因 |
|---|---|---|
| 小型套件(< 5 分钟) | 单个 job,不分片 | 分片开销超过节省的时间 |
| 中型套件(5-20 分钟) | 使用矩阵策略分为 2-4 个分片 | 将实际耗时缩短约 60-75% |
| 大型套件(20+ 分钟) | 4-8 个分片 + blob report 合并 | 将 PR 反馈时间控制在 10 分钟以内 |
| PR 上的跨浏览器测试 | PR 上仅 Chromium;主分支上测试所有浏览器 | PR 上的耗时减少 3 倍 |
| 预发/生产冒烟测试 | 使用 environment: 的独立工作流 |
隔离密钥,添加审批关卡 |
| 夜间完整回归测试 | schedule 触发器 + workflow_dispatch |
全面覆盖,不阻塞 PR |
| 多个仓库,相同配置 | 使用 workflow_call 的可复用工作流 |
DRY 原则;更新一个文件,所有仓库受益 |
| 需要可重现环境 | 使用 Playwright 镜像的容器 job | 与本地 Docker 环境完全一致 |
反模式
| 反模式 | 问题 | 应改为 |
|---|---|---|
没有 concurrency 分组 |
每次推送都会产生重复运行,浪费分钟数 | 添加 concurrency: { group: ..., cancel-in-progress: true } |
分片时使用 fail-fast: true |
一个分片失败会取消其他分片,丢失它们的测试结果 | 设置 fail-fast: false 以收集所有失败 |
| 安装浏览器时未使用缓存 | 每次运行浪费 60-90 秒 | 以 lockfile 哈希为键,缓存 ~/.cache/ms-playwright |
未设置 timeout-minutes |
卡住的 job 将运行 6 小时(GitHub 默认值) | 设置明确的超时时间:20-30 分钟 |
| 仅在失败时上传构建产物 | 测试通过时没有报告,无法验证结果 | 使用 if: ${{ !cancelled() }} 始终上传 |
| 在工作流文件中硬编码密钥 | 安全漏洞 | 使用 GitHub Secrets 和 Environments |
| 在每个 PR 上运行所有浏览器 | 成本增加 3 倍,收益甚微 | PR 上仅 Chromium;合并到主分支时做跨浏览器测试 |
actions/upload-artifact 未设置保留期限 |
默认 90 天保留期会填满存储 | 为报告设置 retention-days: 7-14 |
安装浏览器时未使用 --with-deps |
缺少操作系统库导致浏览器启动失败 | 始终使用 npx playwright install --with-deps |
故障排查
浏览器启动失败:"Missing dependencies"
原因:浏览器从缓存安装,但操作系统依赖未被缓存(它们位于系统目录中,而非 ~/.cache)。
修复:在缓存命中时始终运行 npx playwright install-deps:
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
测试本地通过但 CI 中超时失败
原因:CI 运行器的 CPU 核心数和 RAM 少于你的开发机器。默认的 worker 数和超时时间设置得太激进。
修复:在配置中为 CI 减少 worker 数并增加超时时间:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
workers: process.env.CI ? "50%" : undefined,
use: {
actionTimeout: process.env.CI ? 15_000 : 10_000,
navigationTimeout: process.env.CI ? 30_000 : 15_000,
},
})
分片报告不完整——合并报告中缺少部分分片
原因:使用 actions/download-artifact@v4 时未设置 merge-multiple: true,或者分片之间的构建产物名称冲突。
修复:为每个分片赋予唯一的构建产物名称,并使用 merge-multiple:
# 在每个分片 job 中上传
- uses: actions/upload-artifact@v4
with:
name: blob-report-${{ strategy.job-index }}
path: blob-report/
# 在合并 job 中下载
- uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
webServer 在 CI 中失败:"port 3000 already in use"
原因:前一次运行留下了僵尸进程,或者其他 job 步骤正在使用该端口。
修复:确保在 CI 中设置 reuseExistingServer: false,并添加一个前置步骤来杀死过期进程:
- name: Kill stale processes
run: lsof -ti:3000 | xargs kill -9 2>/dev/null || true
GitHub 注释未在 PR 上显示
原因:未配置 github reporter。
修复:为 CI 运行添加 github reporter:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [["html", { open: "never" }], ["github"]]
: [["html", { open: "on-failure" }]],
})
相关文档
- ci/parallel-and-sharding.md —— 分片策略与 blob report 合并
- ci/reporting-and-artifacts.md —— reporter 配置与构建产物管理
- ci/docker-and-containers.md —— CI 容器镜像
- ci/ci-gitlab.md —— GitLab CI 对应方案
- ci/ci-other.md —— CircleCI、Azure DevOps、Jenkins
- core/configuration.md —— 感知 CI 的配置设置