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

16 KiB
Raw Permalink Blame History

CICircleCI、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     # 多个报告器

模式

模式 1CircleCI

使用场景:你的项目运行在 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

模式 2Azure 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: "上传合并后的报告"

模式 3Jenkins

使用场景:你的项目运行在 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_TOTALCIRCLE_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"

JenkinsDocker 代理中出现 "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

相关文档