项目文件夹

文件
2026-07-13 21:36:47 +08:00

580 行
18 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 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 的配置设置