Files
2026-07-13 21:36:47 +08:00

580 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CIGitHub 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 的配置设置