# CI:GitHub Actions > **使用时机**:在拉取请求、合并到主分支或按计划自动运行 Playwright 测试。GitHub Actions 是 Playwright 项目中最常用的 CI。 ## 快速参考 ```bash # 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 的项目。这是一个完整且经过实战检验的工作流。 ```yaml # .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 分钟以内——分片开销(检出、安装、合并)会抵消其带来的好处。 ```yaml # .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 以便合并分片输出: ```ts // playwright.config.ts import { defineConfig } from "@playwright/test" export default defineConfig({ reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { open: "on-failure" }]], }) ``` ### 模式 3:可复用工作流 **使用时机**:多个仓库或多个工作流文件需要相同的 Playwright 配置。 **避免时机**:只有一个工作流的单个仓库。 ```yaml # .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 ``` **调用可复用工作流:** ```yaml # .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` 已经能正常工作(这是常见情况)。 ```yaml # .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:环境密钥与部署目标 **使用时机**:测试针对需要身份验证凭据的预发/生产环境运行。 **避免时机**:测试只针对本地启动的开发服务器运行。 ```yaml # .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 上运行。 ```yaml # .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`: ```yaml - 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 数并增加超时时间: ```ts // 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`: ```yaml # 在每个分片 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`,并添加一个前置步骤来杀死过期进程: ```yaml - name: Kill stale processes run: lsof -ti:3000 | xargs kill -9 2>/dev/null || true ``` ### GitHub 注释未在 PR 上显示 **原因**:未配置 `github` reporter。 **修复**:为 CI 运行添加 `github` reporter: ```ts // 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](parallel-and-sharding.md) —— 分片策略与 blob report 合并 - [ci/reporting-and-artifacts.md](reporting-and-artifacts.md) —— reporter 配置与构建产物管理 - [ci/docker-and-containers.md](docker-and-containers.md) —— CI 容器镜像 - [ci/ci-gitlab.md](ci-gitlab.md) —— GitLab CI 对应方案 - [ci/ci-other.md](ci-other.md) —— CircleCI、Azure DevOps、Jenkins - [core/configuration.md](../core/configuration.md) —— 感知 CI 的配置设置