把 Playwright 接进 CI:从跑得起来到出报告
本地 npx playwright test 全绿,推上去 CI 一片红,日志里只有一句 Error: Failed to launch browser——这大概是把端到端测试接进流水线时最常见的第一道坎。第二道坎更磨人:测试确实跑了、确实失败了,但你在 CI 日志里只能看到一行断言错误,失败当时页面长什么样、点击落在哪个元素上,全都留在了那台用完即焚的机器里。
Playwright 仓库把这两件事分别写在 docs/src/ci.md 和 docs/src/ci-intro.md 两个文件里。前者是各家 CI 提供商的配置汇总,后者专讲 GitHub Actions 这一条路。下面按这两个文件里写明的内容走一遍。
前置条件:先分清你在哪个语言绑定上
这一段最容易被跳过,但它决定了后面每一行你该抄哪一段。
ci.md 开头把整件事概括成三步:确保 CI agent 能跑浏览器(Linux agent 用官方 Docker 镜像,或者用 CLI 装系统依赖)、安装 Playwright、跑你的测试。三步里的每一步,四种语言绑定的命令都不一样,文档里是用 bash js / bash python / bash java / bash csharp 分块标注的:
- JavaScript:
npm ci,然后npx playwright install --with-deps,最后npx playwright test - Python:
pip install playwright,然后playwright install --with-deps,最后pytest - Java:
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" - .NET:
dotnet build,然后pwsh bin/Debug/netX/playwright.ps1 install --with-deps
--with-deps 是什么意思,docs/src/browsers.md 的「Install system dependencies」一节写得更清楚:install-deps 这个子命令负责装操作系统层面的依赖,文档自述这对 CI 环境有用;而 install --with-deps 是把装浏览器和装系统依赖合成一条命令。也就是说,Linux agent 上你真正在解决的是「系统缺库」这件事,跟 Playwright 本身关系不大。
Windows 与 macOS 这边不一样。 ci.md 在 Azure Pipelines 一节写明:对 Windows 或 macOS 的 agent 不需要额外配置,装上 Playwright 直接跑测试即可。至于 GitHub Actions 上的 Windows runner 该怎么写,ci.md 与 ci-intro.md 里给的示例 job 全部是 runs-on: ubuntu-latest,仓库里没有找到 Windows runner 的现成 workflow 示例。
另外一件 Windows 用户迟早会碰到的事:浏览器二进制被下到哪儿。browsers.md 写明 Windows 上是 %USERPROFILE%\AppData\Local\ms-playwright,macOS 是 ~/Library/Caches/ms-playwright,Linux 是 ~/.cache/ms-playwright;要改位置用 PLAYWRIGHT_BROWSERS_PATH,文档给了三种 shell 的写法,Windows 侧分别是 set PLAYWRIGHT_BROWSERS_PATH=%USERPROFILE%\pw-browsers 和 $Env:PLAYWRIGHT_BROWSERS_PATH="$Env:USERPROFILE\pw-browsers"。浏览器是随 Playwright 版本绑定下发的,所以升级 Playwright 会带来新的浏览器目录——这也是下面「不建议缓存」那条的背景。
步骤:workflow 片段与每一步在改什么
ci.md 里 JavaScript 的 push / pull_request workflow 原文如下:
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
ci-intro.md 把这几步的语义列了出来:克隆仓库、装 Node.js、装 npm 依赖、装 Playwright 浏览器、跑测试、把 HTML 报告上传到 GitHub 界面。其中 timeout-minutes: 60 和 retention-days: 30 是仓库示例里给的值,不是必须照搬的规定值。
最后那个 step 是本文的重点。path: playwright-report/ 对应的是 HTML reporter 的默认输出目录——docs/src/test-reporters-js.md 写明报告默认写进当前工作目录下的 playwright-report 文件夹,可以用 PLAYWRIGHT_HTML_OUTPUT_DIR 或 reporter 配置覆盖(这是仓库当前文档里的默认值,随版本可能变动)。if: ${{ !cancelled() }} 这个条件的作用是:测试失败时这一步照样执行——如果不写,失败的那次运行反而拿不到报告,等于白配。
Python 的 workflow 长得不一样,别照着 JS 那份改。 ci.md 里 Python 那块是这样的:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Ensure browsers are installed
run: python -m playwright install --with-deps
- name: Run your tests
run: pytest --tracing=retain-on-failure
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-traces
path: test-results/
(python-version: '3.13' 是仓库示例里给的值。)差别有两处值得注意:一是跑测试那一步带了 --tracing=retain-on-failure,docs/src/test-runners-python.md 写明 --tracing 接受 on、off、retain-on-failure,默认是 off(仓库当前文档里的默认值,随版本可能变动);二是上传的目录是 test-results/、artifact 名字叫 playwright-traces,上传的是 trace 而不是 HTML 报告。
JavaScript 这边打开 trace 走的是配置文件。docs/src/test-use-options-js.md 的「Recording Options」一节给的示例是在 use 里设置:
export default defineConfig({
use: {
// Capture screenshot after each test failure.
screenshot: 'only-on-failure',
// Record trace only when retrying a test for the first time.
trace: 'on-first-retry',
// Record video only when retrying a test for the first time.
video: 'on-first-retry'
},
});
同一文件的选项表里,TestOptions.trace 一行写明的取值是 'off'、'on'、'retain-on-failure'、'on-first-retry';再往下的行为对照表里还出现了 'retain-on-failure-and-retries',所以别把那一行当成取值的全集,以仓库最新内容为准。同一节还写明 trace、截图、视频都会出现在测试输出目录里,通常是 test-results。所以如果你既要 HTML 报告又要 trace 文件本身,上传路径要覆盖到这两处。
还有两个只在 CI 上才需要动的配置。一个是并发:ci.md 建议在 CI 环境把 workers 设成 1 以优先保证稳定性与可复现性(这是仓库文档自述的理由),示例写法是 workers: process.env.CI ? 1 : undefined;docs/src/test-api/class-testconfig.md 里 TestConfig.workers 自 v1.10 起提供,类型是 ?<int|string>,也接受 '50%' 这样的百分比。另一个是 reporter:test-reporters-js.md 写明 dot 是 CI 上的默认 reporter,同时给了 reporter: process.env.CI ? 'github' : 'list',github reporter 会生成 GitHub Actions 的 annotation。HTML 报告的 open 默认是 'on-failure'(同上,属仓库当前文档里的默认值),在 CI 上通常配成 reporter: [['html', { open: 'never' }]]。
要分片跑,docs/src/test-sharding-js.md 的 GitHub Actions 示例是用 strategy.matrix 的 shardIndex / shardTotal,每个分片把 blob-report 上传成 blob-report-${{ matrix.shardIndex }},再由一个 needs: [playwright-tests] 的合并 job 下载全部分片、跑 npx playwright merge-reports --reporter html ./all-blob-reports。前提是配置里写了 reporter: process.env.CI ? 'blob' : 'html'。
另外两种触发方式
ci.md 在 GitHub Actions 这一节还给了两个变体,用得上的时候不必自己发明。
一是 Via Containers:用 GitHub Actions 的 jobs.<job_id>.container 选项把整个 job 跑在容器里,文档自述这样做的好处是不往宿主环境里塞依赖,并且在不同操作系统之间获得一致的环境,对截图与视觉回归测试有意义。示例里 job 结构变成了 container: 加 image: 与 options: --user 1001,镜像名在文档里写成带占位符的形式,四种语言各有各的镜像(JavaScript 用 mcr.microsoft.com/playwright,其余三种分别在后面接 /python、/java、/dotnet)。注意用容器之后,示例里就没有单独的 npx playwright install --with-deps 这一步了——浏览器和系统依赖已经在镜像里。
二是 On deployment:把触发条件换成 on: deployment_status,并加上 if: github.event.deployment_status.state == 'success',等一次 GitHub Deployment 进入 success 状态之后再跑测试。ci.md 写明 Vercel 这类服务用的就是这个模式,于是你可以直接对着已部署的环境跑端到端测试;示例里通过 env 把 PLAYWRIGHT_TEST_BASE_URL 设成 ${{ github.event.deployment_status.target_url }},Python 那份还额外注了一句「这可能取决于你的 test-runner」。
边界:这些地方文档自己划了线
- 浏览器缓存不推荐。
ci.md的「Caching browsers」一节写明:不建议缓存浏览器二进制,因为恢复缓存的耗时与重新下载相当;尤其在 Linux 上,操作系统依赖本身是缓存不了的。真要缓存,文档说按 Playwright 版本的哈希去缓存browsers.md里列出的那几个目录。 - headed 模式在 Linux agent 上要 Xvfb。
ci.md写明 Playwright 默认以 headless 启动;Linux agent 上跑 headed 需要装 Xvfb,官方 Docker 镜像和 GitHub Action 里已预装,命令前加xvfb-run即可,例如xvfb-run npx playwright test。 - 两个文件里 action 的版本标记不一致。
ci.md的 workflow 里上传步骤写的是actions/upload-artifact@v5,而ci-intro.md同一步写的是actions/upload-artifact@v4;test-sharding-js.md的分片示例里也是@v4,下载用的是actions/download-artifact@v5。这只是同一仓库不同文档页的状态差异,抄的时候以你参照的那一页为准,并以仓库最新内容为准。 --only-changed是启发式。ci.md的 Fail-Fast 一节写明该参数靠分析依赖图来判断哪些测试文件受改动影响,文档明说这是启发式、可能漏掉测试,因此预跑之后仍然必须跑全量。- 把报告发布到 Azure 静态站点那条路对 fork 的 PR 不生效。
ci-intro.md在那一节的 note 里写明:来自 fork 仓库的 pull request 拿不到 secrets,因此该步骤不会工作。 - 产物本身是敏感的。
ci-intro.md的「Properly handling Secrets」一节写明:trace 文件、HTML 报告乃至控制台日志都包含测试执行信息,可能含测试用户的凭据、访问 staging 后端的 token、测试源码甚至应用源码;上传时应只上传到可信的 artifact 存储,或先加密,分享给同事时同理。
怎么验证配对了
按 ci-intro.md 的说法,推上去之后点仓库的 Actions 标签页看 workflow 运行结果,pull request 上也可以从状态检查的 Details 链接进去;点开某次运行、再点 Run Playwright tests 那一步,能看到错误信息、期望值与实际值以及 call log。
报告要落地看,还得再走一步。ci-intro.md 写明:在 Artifacts 区域点 playwright-report 下载得到的是一个 zip;本地直接打开报告是不行的,需要一个 web 服务器,正确做法是解压后用
npx playwright show-report name-of-my-extracted-playwright-report
来起服务查看。test-reporters-js.md 补了一句:也可以直接把从 CI 产物下载来的 .zip 传给它,前提是压缩包顶层含 index.html,Playwright 会解压到临时目录再提供服务。至于这一步在 Windows 上是否有额外要求,ci-intro.md 与 test-reporters-js.md 里都没有找到相关说明,文档只强调了「要解压、要用 show-report 起服务」这两件事,并建议解压到一个已经装好 Playwright 的目录里。
trace 那边分两种情况:JavaScript 侧是先 npx playwright show-report,然后点测试文件名旁边的 trace 图标;Python 与 Java 侧,ci-intro.md 指的是 trace.playwright.dev——Trace Viewer 的静态托管版本,把 trace 文件拖进去就能看。C# 侧文档额外强调这要求你自己启停 trace,并建议只为失败的测试录制。
如果卡在最早那一步,浏览器根本起不来,ci.md 给的排查手段是 DEBUG 环境变量:DEBUG=pw:browser npx playwright test(Python 侧对应 DEBUG=pw:browser pytest),文档明说它对排查 Error: Failed to launch browser 有用。注意这是 bash 的前缀写法;在 PowerShell 下如何设置这个变量,ci.md 里没有找到相关说明。
以上片段均原样取自仓库文档;若你把不同小节的参数拼到一起使用,请注意这是按仓库文档中的参数语义组合的示例,未经实测,以仓库最新内容与 --help 的实际输出为准。安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。
本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测,
因此不涉及运行速度、稳定性与实际表现的任何描述。
该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。