16 KiB
16 KiB
CI:CircleCI、Azure DevOps 与 Jenkins
何时使用:在 GitHub Actions 或 GitLab 以外的 CI 平台中运行 Playwright 测试。每个部分均提供一份可直接复制并适配的生产级配置。
快速参考
# 所有 CI 平台通用
npx playwright install --with-deps # 安装浏览器 + 操作系统依赖
npx playwright test --shard=1/4 # 分片以实现并行
npx playwright merge-reports ./blob-report # 合并分片结果
npx playwright test --reporter=dot,html # 多个报告器
模式
模式 1:CircleCI
使用场景:你的项目运行在 CircleCI 上。
基础流水线
# .circleci/config.yml
version: 2.1
executors:
playwright:
docker:
- image: mcr.microsoft.com/playwright:v1.52.0-noble
working_directory: ~/project
jobs:
install:
executor: playwright
steps:
- checkout
- restore_cache:
keys:
- npm-deps-{{ checksum "package-lock.json" }}
- run: npm ci
- save_cache:
key: npm-deps-{{ checksum "package-lock.json" }}
paths:
- node_modules
- persist_to_workspace:
root: .
paths:
- node_modules
test:
executor: playwright
parallelism: 4
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Run Playwright tests
command: |
npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
- store_artifacts:
path: playwright-report
destination: playwright-report
- store_artifacts:
path: test-results
destination: test-results
- store_test_results:
path: test-results/junit.xml
workflows:
test:
jobs:
- install
- test:
requires:
- install
CircleCI JUnit 集成配置:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [["dot"], ["html", { open: "never" }], ["junit", { outputFile: "test-results/junit.xml" }]]
: [["html", { open: "on-failure" }]],
})
CircleCI 使用 Orbs(简化版)
# .circleci/config.yml
version: 2.1
orbs:
node: circleci/node@6.1
executors:
playwright:
docker:
- image: mcr.microsoft.com/playwright:v1.52.0-noble
jobs:
e2e:
executor: playwright
parallelism: 4
steps:
- checkout
- node/install-packages
- run:
name: Run tests
command: npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
- store_artifacts:
path: playwright-report
- store_test_results:
path: test-results/junit.xml
workflows:
main:
jobs:
- e2e
模式 2:Azure DevOps
使用场景:你的项目运行在 Azure DevOps Pipelines 上。
基础流水线
# azure-pipelines.yml
trigger:
branches:
include:
- main
pr:
branches:
include:
- main
pool:
vmImage: "ubuntu-latest"
variables:
CI: "true"
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: NodeTool@0
inputs:
versionSpec: "20.x"
displayName: "安装 Node.js"
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
displayName: "缓存 npm"
- script: npm ci
displayName: "安装依赖"
- script: npx playwright install --with-deps
displayName: "安装 Playwright 浏览器"
- script: npx playwright test
displayName: "运行 Playwright 测试"
- task: PublishTestResults@2
condition: always()
inputs:
testResultsFormat: "JUnit"
testResultsFiles: "test-results/junit.xml"
mergeTestResults: true
testRunTitle: "Playwright Tests"
displayName: "发布测试结果"
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: playwright-report
artifact: playwright-report
publishLocation: "pipeline"
displayName: "上传报告"
Azure DevOps JUnit 集成配置:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [["dot"], ["html", { open: "never" }], ["junit", { outputFile: "test-results/junit.xml" }]]
: [["html", { open: "on-failure" }]],
})
Azure DevOps 分片
# azure-pipelines.yml
trigger:
branches:
include:
- main
pr:
branches:
include:
- main
variables:
CI: "true"
stages:
- stage: Test
jobs:
- job: Playwright
pool:
vmImage: "ubuntu-latest"
strategy:
matrix:
shard1:
SHARD: "1/4"
shard2:
SHARD: "2/4"
shard3:
SHARD: "3/4"
shard4:
SHARD: "4/4"
steps:
- task: NodeTool@0
inputs:
versionSpec: "20.x"
- script: npm ci
displayName: "安装依赖"
- script: npx playwright install --with-deps
displayName: "安装浏览器"
- script: npx playwright test --shard=$(SHARD)
displayName: "运行测试(分片 $(SHARD))"
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: blob-report
artifact: blob-report-$(System.JobPositionInPhase)
displayName: "上传 blob 报告"
- stage: Report
dependsOn: Test
condition: always()
jobs:
- job: MergeReports
pool:
vmImage: "ubuntu-latest"
steps:
- task: NodeTool@0
inputs:
versionSpec: "20.x"
- script: npm ci
displayName: "安装依赖"
- task: DownloadPipelineArtifact@2
inputs:
patterns: "blob-report-*/**"
path: all-blob-reports
displayName: "下载所有 blob 报告"
- script: npx playwright merge-reports --reporter=html ./all-blob-reports
displayName: "合并报告"
- task: PublishPipelineArtifact@1
inputs:
targetPath: playwright-report
artifact: playwright-report
displayName: "上传合并后的报告"
模式 3:Jenkins
使用场景:你的项目运行在 Jenkins 上。
Jenkinsfile(声明式流水线)
// Jenkinsfile
pipeline {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root' // Playwright 在容器中需要 root 权限
}
}
environment {
CI = 'true'
HOME = '/root'
npm_config_cache = "${WORKSPACE}/.npm"
}
options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Test') {
steps {
sh 'npx playwright test'
}
post {
always {
// 发布 JUnit 结果
junit allowEmptyResults: true,
testResults: 'test-results/junit.xml'
// 归档 HTML 报告
archiveArtifacts artifacts: 'playwright-report/**',
allowEmptyArchive: true
// 失败时归档追踪记录
archiveArtifacts artifacts: 'test-results/**',
allowEmptyArchive: true
}
}
}
}
post {
failure {
// 失败时通知(Slack、电子邮件等)
echo 'Playwright 测试失败!'
}
cleanup {
cleanWs()
}
}
}
Jenkins JUnit 集成配置:
// playwright.config.ts
import { defineConfig } from "@playwright/test"
export default defineConfig({
reporter: process.env.CI
? [["dot"], ["html", { open: "never" }], ["junit", { outputFile: "test-results/junit.xml" }]]
: [["html", { open: "on-failure" }]],
})
Jenkins 并行阶段
// Jenkinsfile(分片)
pipeline {
agent none
environment {
CI = 'true'
HOME = '/root'
}
options {
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage('Test') {
parallel {
stage('Shard 1') {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root'
}
}
steps {
sh 'npm ci'
sh 'npx playwright test --shard=1/4'
}
post {
always {
archiveArtifacts artifacts: 'blob-report/**',
allowEmptyArchive: true
}
}
}
stage('Shard 2') {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root'
}
}
steps {
sh 'npm ci'
sh 'npx playwright test --shard=2/4'
}
post {
always {
archiveArtifacts artifacts: 'blob-report/**',
allowEmptyArchive: true
}
}
}
stage('Shard 3') {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root'
}
}
steps {
sh 'npm ci'
sh 'npx playwright test --shard=3/4'
}
post {
always {
archiveArtifacts artifacts: 'blob-report/**',
allowEmptyArchive: true
}
}
}
stage('Shard 4') {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root'
}
}
steps {
sh 'npm ci'
sh 'npx playwright test --shard=4/4'
}
post {
always {
archiveArtifacts artifacts: 'blob-report/**',
allowEmptyArchive: true
}
}
}
}
}
}
}
决策指南
| CI 平台 | Docker 镜像支持 | 原生并行方式 | 产物浏览 | JUnit 集成 |
|---|---|---|---|---|
| CircleCI | 一流支持(docker: 执行器) |
parallelism: N 配合 CIRCLE_NODE_INDEX |
通过产物标签页 | store_test_results |
| Azure DevOps | 通过 vmImage 或容器任务 |
strategy.matrix |
Pipeline Artifacts 界面 | PublishTestResults@2 |
| Jenkins | Docker Pipeline 插件 | parallel 阶段 |
归档产物 | junit 步骤 |
| 场景 | CircleCI | Azure DevOps | Jenkins |
|---|---|---|---|
| 分片变量 | $((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL |
在矩阵中定义:SHARD: '1/4' |
在每个并行阶段中硬编码 |
| 缓存键 | checksum "package-lock.json" |
Cache@2 配合键模板 |
stash/unstash 或共享卷 |
| 密钥 | Context + 环境变量 | 变量组 + 流水线变量 | Credentials 插件 |
| 报告上传 | store_artifacts |
PublishPipelineArtifact@1 |
archiveArtifacts |
反模式
| 反模式 | 问题 | 应改为 |
|---|---|---|
在裸金属上安装浏览器时未使用 --with-deps |
缺少操作系统库导致启动失败 | 使用 Playwright Docker 镜像或 --with-deps 标志 |
| 未配置 JUnit 报告器 | CI 平台无法原生显示测试结果 | 添加 ['junit', { outputFile: 'test-results/junit.xml' }] |
| 任务没有超时限制 | 挂起的测试无限运行,消耗 CI 资源 | 设置明确的超时时间(20-30 分钟) |
| 测试通过时不上传产物 | 测试通过后无法验证结果 | 始终上传报告(condition: always() / when: always) |
| 在容器中以非 root 用户运行浏览器但未做配置 | 浏览器二进制文件出现权限错误 | 以 root 身份运行或配置正确的权限 |
| 在配置中硬编码分片数量而非使用 CI 变量 | 更改并行度时需要修改两处 | 使用 CI 原生变量(CI_NODE_TOTAL、CIRCLE_NODE_TOTAL) |
故障排除
CircleCI:"Error: browserType.launch: Executable doesn't exist"
原因:未使用 Playwright Docker 镜像,或镜像版本与 @playwright/test 版本不匹配。
修复:将镜像标签与 Playwright 版本对齐:
docker:
- image: mcr.microsoft.com/playwright:v1.52.0-noble # 与 package.json 中的版本匹配
Azure DevOps:测试结果未显示在 Tests 标签页中
原因:未配置 JUnit 报告器,或缺少 PublishTestResults@2 任务。
修复:同时添加报告器和发布任务:
// playwright.config.ts
reporter: [['junit', { outputFile: 'test-results/junit.xml' }]],
- task: PublishTestResults@2
condition: always()
inputs:
testResultsFormat: "JUnit"
testResultsFiles: "test-results/junit.xml"
Jenkins:Docker 代理中出现 "Browser closed unexpectedly"
原因:在容器中以非 root 用户运行。Chromium 的沙箱需要 root 权限或 --no-sandbox。
修复:在 Docker 代理中以 root 身份运行:
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.52.0-noble'
args '-u root'
}
}
environment {
HOME = '/root'
}
所有平台:分片索引偏移一位
原因:CircleCI 的 CIRCLE_NODE_INDEX 是从 0 开始计数的,但 Playwright 的 --shard 是从 1 开始计数的。
修复:在 CircleCI 中将索引加 1:
# CircleCI
command: npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
# GitLab(已是从 1 开始计数)
command: npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
相关文档
- ci/ci-github-actions.md —— GitHub Actions 配置
- ci/ci-gitlab.md —— GitLab CI 配置
- ci/parallel-and-sharding.md —— 分片策略
- ci/docker-and-containers.md —— Docker 镜像详情
- ci/reporting-and-artifacts.md —— CI 报告器配置