Chromium、Firefox 与 WebKit:把三浏览器差异变成可解释的 CI 矩阵
合并请求里的结账用例在开发机上连续通过,进入 Linux CI 后却出现三种结果:Chromium 是绿色,Firefox 找不到刚刚出现的按钮,WebKit 的日期断言偏了一天。维护者先把重试从 0 调到 2,流水线偶尔全绿,第二天同一提交又红。这个现场真正暴露的不是“某个浏览器不稳定”,而是测试把浏览器实现、操作系统、字体、语言、时区、分辨率、账号和数据状态混成了一个不可解释的环境。
三浏览器验证的价值也不在于多跑两遍。它要回答三个更具体的问题:同一用户结果是否能跨引擎成立;差异来自产品代码、浏览器能力还是测试假设;团队愿意用多少时间、算力和维护成本换取多大的兼容风险下降。Playwright 把一次配置展开成多个 project,每个 project 再启动 worker、browser、context 和 page。只要把环境输入、失败附件和放行规则绑定到这些对象,红灯就能从“玄学兼容问题”变成可归类的工程证据。
先辨认自动化实际启动了什么
Chromium、Firefox 和 WebKit 在 Playwright 中首先是三套受版本约束的浏览器二进制,而不是三个随系统自动升级的桌面图标。每个 Playwright 版本要求特定 browser revision;升级 @playwright/test 后若没有重新执行安装,常见结果不是继续使用旧浏览器,而是启动时报告 executable 不存在。官方的浏览器安装与管理说明明确给出了这一绑定关系、默认缓存目录、代理变量、垃圾回收和卸载入口。
Chromium project 默认使用 Playwright 随版本绑定的开源 Chromium 构建;无 channel 的 headless 运行通常启动单独下载的 Chromium headless shell,headed 运行使用常规 Chromium。设置 channel: 'chromium' 才会选择更接近常规浏览器的新 headless 模式,设置 channel: 'chrome'、channel: 'msedge' 等则运行机器上对应的品牌渠道。三者不是可互换标签:headless shell、新 headless、品牌 Stable/Beta 可能表现不同,报告和基线名称必须保留 channel 与 headless/headed 身份。Firefox 使用 Playwright 打过补丁、与近期 Firefox Stable 对齐的构建,官方明确说明不能直接驱动品牌 Firefox。WebKit 来自主线源码并带有 Playwright 所需补丁,也不是品牌 Safari。它能提前发现大量 Safari 相关问题,却不能把“某个平台上的 WebKit 通过”写成“真实 Safari 已通过”。这些构建与品牌浏览器的关系、可用渠道和平台差异都以官方浏览器支持说明为准。
同样,devices['Desktop Safari'] 是一组 browser/context 参数,不是把 WebKit 变成一台真实 Mac。device descriptor 会带入 browserName、user agent、viewport、screen、device scale factor、触摸等参数;官方设备模拟说明还特别指出预配置桌面设备可能带有特定平台的 user agent。于是报告里看到 Macintosh 并不能证明 runner 是 macOS,看到移动 viewport 也不能证明触摸、输入法和硬件加速与真机完全一致。
初学者可以把四个层次分开记:Playwright browser build 与 channel 决定实际执行的构建;宿主操作系统决定字体、媒体 codec、图形栈和原生集成;browser context 决定 cookie、权限、语言、时区、viewport 等会话环境;project 决定哪组测试使用哪套 build/context/重试/并发配置。定位问题时先问是哪一层变了,不要先给失败贴上“Firefox 兼容”标签。尤其是音视频、字体和原生控件问题,官方说明其能力会随 Linux、macOS、Windows 改变;默认开源 Chromium 也不包含品牌 Chrome/Edge 因许可而捆绑的全部媒体 codec。涉及正式支持的音视频格式时,应以目标操作系统上的目标品牌渠道建立行为用例,不能用 Chromium 通过代替 Chrome/Edge codec 结论。接近 Safari 的媒体验证应优先在 macOS 上运行 WebKit,但最终品牌 Safari 放行仍需单独的真实浏览器或设备验证。
把 Node、平台依赖和三套浏览器一次装对
先在仓库里确认锁文件和 Node,再安装测试包。Playwright 的安装页与系统要求列出的 Node 支持线是最新的 22.x、24.x 或 26.x;桌面 Windows、Windows Server、macOS 以及 Debian、Ubuntu 都只应选择该 Playwright 版本明确列出的受支持发行线。团队应把目标 Playwright 版本的系统要求页和 lockfile 一起作为升级输入,因为最低系统版本会随发布变化。
node --version
npm --version
npm ci
# 新项目可生成配置和样例;已有项目通常只安装依赖并保留现有配置
npm init playwright@latest
# 安装锁文件所需的三套浏览器
npx playwright --version
npx playwright install chromium firefox webkit
npx playwright install --listLinux runner 还需要图形、字体、媒体和系统库。npx playwright install --with-deps 会同时安装浏览器与依赖,install-deps 可以只处理系统依赖;在受控镜像构建阶段使用它们,比测试运行到一半才临时执行 apt 更容易审计。无管理员权限的共享 runner 不应让测试作业尝试改系统包,应由基础镜像 owner 预装依赖,并用 npx playwright install-deps --dry-run 检查缺口。官方命令行参考说明 --dry-run 在 Linux 上可用于模拟依赖安装并在缺依赖时给出非零结果。
# Linux 镜像构建阶段
npx playwright install --with-deps chromium firefox webkit
# 只检查系统依赖,不在普通测试作业里提权
npx playwright install-deps --dry-run chromium firefox webkit
# 浏览器启动失败时打开启动日志
DEBUG=pw:browser npx playwright test --project=webkit-build --workers=1企业代理环境还要分清 npm registry 和浏览器 CDN。npm 包安装成功不代表 browser zip 能下载;浏览器下载默认走 Microsoft CDN,可通过 HTTPS_PROXY 指向代理。代理重签证书导致 self signed certificate in certificate chain 时,应把经过安全团队批准的根证书路径交给 NODE_EXTRA_CA_CERTS,不能用关闭 TLS 校验换绿色。证书、代理用户名和密码只放在 runner secret 或受控镜像构建上下文中,不能进入命令日志与缓存键。
用 projects 固定浏览器与环境字段
project 是“同一组测试 + 一组已命名配置”。官方Projects 指南说明,未指定 --project 时会执行全部 project,指定后可以只跑一个;project 还能表达设备、环境、重试、超时和依赖关系。一个可靠的三浏览器基线不只写三行 browserName,还要固定会改变页面行为的环境字段。
import { defineConfig } from '@playwright/test'
const sharedUse = {
baseURL: process.env.E2E_BASE_URL ?? 'http://127.0.0.1:4173',
locale: 'zh-CN',
timezoneId: 'Asia/Shanghai',
viewport: { width: 1280, height: 720 },
colorScheme: 'light' as const,
reducedMotion: 'reduce' as const,
trace: 'on-first-retry' as const,
screenshot: 'only-on-failure' as const,
}
export default defineConfig({
testDir: './e2e',
outputDir: './test-results',
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
use: sharedUse,
projects: [
{ name: 'chromium-build', use: { browserName: 'chromium' } },
{ name: 'firefox-build', use: { browserName: 'firefox' } },
{ name: 'webkit-build', use: { browserName: 'webkit' } },
],
})这份基线故意只让 browserName 变化:三个 project 共享同一 locale、timezone、viewport、颜色和动画设置,差异更容易归因到浏览器构建。官方示例常用 devices['Desktop Chrome']、Desktop Firefox 和 Desktop Safari 快速建矩阵;那种写法会同时引入 descriptor 的 user agent、screen 和其他 context 参数,更适合模拟典型设备,不适合做第一层单变量归因。需要设备覆盖时另建 project;若展开 descriptor 后还要覆盖字段,应先展开 descriptor,再写自定义 viewport、locale 等字段,并在测试中读取最终值验证合并结果。
locale 影响 Intl、navigator.language、请求头等浏览器侧行为;timezoneId 只模拟浏览器时区,不会自动改变 Node 测试进程时区。官方说明中明确提醒 runner 时区要另用 TZ 环境变量设置。日期断言若一半在页面执行、一半在 Node 生成期望值,就应同时固定 browser timezone 与 runner TZ,或者把期望值由同一时区库生成。viewport 是页面布局视口,不等于宿主机显示器分辨率;视频尺寸、截图像素和 device scale factor 还会继续影响证据大小。
每次矩阵运行还应附一份环境清单,至少记录 lockfile 对应的 Playwright 版本、playwright install --list 输出、project 名、browserName、channel、headless/headed、runner 操作系统与 CPU 架构、镜像摘要、字体包或字体清单摘要、locale、browser timezone、runner TZ、viewport 与 device scale factor。字体清单应来自镜像构建记录或 runner 的确定性命令,不把包含用户目录和内部路径的原始输出公开。没有这份清单,同名 chromium-build 在镜像升级后可能已经不是原来的比较环境。
正向实验:证明三套环境都能交付同一用户结果
先用不依赖公网和业务账号的页面建立基线。下面的测试把语言、时区与 viewport 作为可观察状态,同时只断言用户能看到标题并操作按钮。它避免断言某个引擎的 user agent 文本,也不把像素完全一致误当成功能正确。
import { expect, test } from '@playwright/test'
test('固定环境能交付同一用户结果', async ({ page, browserName }, testInfo) => {
await page.setContent(`
<main>
<h1>Checkout</h1>
<button type="button">Submit order</button>
</main>
`)
const environment = await page.evaluate(() => ({
locale: Intl.DateTimeFormat().resolvedOptions().locale,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
viewport: [innerWidth, innerHeight],
userAgent: navigator.userAgent,
}))
await testInfo.attach(`environment-${browserName}`, {
body: Buffer.from(JSON.stringify(environment, null, 2)),
contentType: 'application/json',
})
await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Submit order' })).toBeEnabled()
expect(environment.timezone).toBe('Asia/Shanghai')
expect(environment.viewport).toEqual([1280, 720])
})执行 npx playwright test -g "固定环境",预期看到三个带 project 前缀的通过项,附件中的 timezone 与 viewport 分别是 Asia/Shanghai 和 1280 × 720,user agent 则随构建变化。如果不是三个用例,先用 npx playwright test --list 检查展开后的 project;如果环境字段不同,检查是否还有另一个配置文件、命令行参数或 project use 覆盖了顶层设置。这组证据只能证明“环境字段已进入 browser context、用户结果跨三套 Playwright 构建成立”,不能证明品牌 Firefox、品牌 Safari、企业浏览器策略或所有屏幕宽度都成立。
正向用例若只断言 page.goto() 没报错,证据仍然过弱。网络返回一个错误页也能成功导航,按钮存在但被遮挡也可能被弱选择器找到。优先使用 role、label 和可见文本定位,配合 web-first assertion 等待用户可见结果;协议响应、下载内容和持久化状态则用对应的网络或文件断言补充。
反向实验:让 Chromium 假设稳定暴露
跨浏览器用例最常见的伪契约是把 Chromium 的表现写成产品要求。例如代码通过 navigator.userAgent.includes('Chrome') 选择功能,测试也断言 UA 必须包含 Chrome/,于是 Chromium 自证正确,Firefox 与 WebKit 被错误拒绝。可以保留一个故意失败的实验来证明矩阵确实能抓住这种假设。
import { expect, test } from '@playwright/test'
test('Chromium UA 不是跨浏览器契约', async ({ page }) => {
await page.setContent('<p>Browser capability must be tested by behavior.</p>')
const userAgent = await page.evaluate(() => navigator.userAgent)
expect(userAgent, '这里故意暴露 Chromium-only 假设').toContain('Chrome/')
})运行三个 project 时,Chromium 构建通常通过,Firefox 与 WebKit 构建会在同一断言行失败;具体 UA 版本由当前 Playwright 绑定的 browser revision 决定,不应写进长期断言。重试后若仍以相同机制失败,说明它不是等待时序造成的 flaky。正确修复不是给 Firefox/WebKit 加跳过,而是让产品按能力与用户结果判断,并把测试改成相应行为断言。
反例的价值在于验证门禁真的观察了差异。若故意写入 Chromium-only 假设后三个 project 仍然绿色,应检查 project 是否真正执行、配置是否被另一个文件覆盖、--project 过滤是否误用、报告是否只展示一个 shard,或者测试是否在运行前被 skip。负向控制修回后要重新执行全矩阵,确认失败证据消失,而不是删除失败 project。
把差异分成可处理的故障类别
浏览器名字只是维度,差异类型才决定修复 owner。第一类是产品行为差异:标准 API、表单、导航、下载、弹窗、剪贴板或权限在某个引擎下不成立。这类问题要用用户结果和能力检测复现,不能用更长超时掩盖。第二类是渲染差异:字体回退、默认控件、滚动条、子像素布局和图像解码改变截图或命中区域;功能断言与视觉基线应分开归因。
第三类是环境差异。语言会改变文案、数字和日期,时区会改变边界日,viewport 与 device scale factor 会改变响应式分支,colorScheme 和 reducedMotion 会改变样式与动画。第四类是测试实现差异,例如依赖 DOM 顺序、瞬时 class、坐标点击、固定 sleep、UA 文本或浏览器私有快捷键。第五类是 runner 差异:系统依赖、字体、共享内存、sandbox、代理、CA、DNS、CPU 抢占和磁盘不足。
排障时先比较失败 project 的配置摘要,再看 trace 中的动作、DOM snapshot、console 与 network。若同一网络响应在三浏览器一致,只有元素尺寸不同,优先查字体和布局;若 WebKit 根本没有发出请求,查前置交互、权限和 JS 异常;若 browser 在创建 page 前退出,查二进制、系统库、sandbox、共享内存和磁盘,不要先改 locator。
可以把每个跨浏览器缺陷记录为“用户结果、失败 project、首次失败步骤、环境字段、网络/console 证据、最小复现、临时处置、修复 owner”。“Firefox 不稳定”无法驱动修复,“Firefox project 在 1280 × 720、zh-CN 下点击提交后未发出 POST,console 报 API 不存在”才可以。
接进项目时让账号、数据和上下文保持隔离
真实项目通常需要登录态和测试数据。不要让三 project 共用一个会被修改的账号:project 可以并行,worker 失败后还会重启,重试会再次执行动作。共享购物车、草稿、验证码或一次性 token 会让浏览器差异与数据竞争叠加。最稳妥的入口是为每个 worker 分配唯一测试账号,并让数据命名空间包含 run、project、parallel index、test id 与 retry,在 teardown 或外部清理作业中按同一标识删除。
复用认证状态时,storageState 可能包含可冒用账号的 cookie 和 header。Playwright 的认证指南要求把认证状态目录加入 .gitignore,并明确警告不要提交这些文件。多浏览器能否共用同一份状态取决于应用认证机制;如果状态在某个 browser project 中生成并被所有 project 读取,要验证 cookie 的 domain、SameSite、Secure 与过期行为,而不是默认它跨浏览器完全等价。
import { createHash } from 'node:crypto'
import { test as base } from '@playwright/test'
export const test = base.extend<{ dataNamespace: string }>({
dataNamespace: async ({}, use, testInfo) => {
const safe = (value: string) =>
value.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 48)
const runId = safe(process.env.E2E_RUN_ID ?? `local-${process.pid}`)
const testId = createHash('sha256')
.update(testInfo.testId)
.digest('hex')
.slice(0, 16)
const value = [
'e2e',
runId,
safe(testInfo.project.name),
testInfo.parallelIndex,
testId,
testInfo.retry,
].join('-')
await use(value)
await deleteTestDataByNamespace(value)
},
})清理动作必须幂等:资源不存在时视为已清理,资源仍被后台任务占用时进行有限重试,超过上限留下资源 ID 的脱敏摘要并报警。测试失败后直接跳过清理,会让下一轮得到污染数据;在 afterEach 中清理也可能因 worker 进程崩溃而不执行,所以共享环境还需要按命名空间和 TTL 运行独立清扫。
分层 CI 不等于把低频浏览器降为摆设
全量用例 × 三浏览器 × 多语言 × 多视口会很快形成组合爆炸。架构选择应从风险分层,而不是让所有提交都跑全部组合,也不是把 Firefox/WebKit 永远放到无人看的夜间任务。一个实用层次是:提交前或 PR 快速层跑 Chromium 核心路径;合并门禁层跑三浏览器的登录、支付、上传、下载、导航和关键布局;周期层扩展语言、时区、移动设备、权限和长路径;浏览器或 Playwright 升级分支再跑全量兼容集。
name: browser-matrix
on: [pull_request]
permissions:
contents: read
jobs:
critical-paths:
strategy:
fail-fast: false
matrix:
include:
- project: chromium-build
browser: chromium
- project: firefox-build
browser: firefox
- project: webkit-build
browser: webkit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx playwright install --with-deps ${{ matrix.browser }}
- run: npx playwright test --project=${{ matrix.project }} --grep @critical --reporter=html
env:
CI: "true"
TZ: Asia/Shanghai
E2E_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
PLAYWRIGHT_HTML_OPEN: never
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: evidence-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.project }}
path: playwright-report/
if-no-files-found: warn
retention-days: 7fail-fast: false 让一个 project 红时其他浏览器继续产出对照证据;这会增加已知失败时的计算量,却能避免只知道“第一个失败者”。permissions: contents: read 限制工作流令牌默认能力,但测试系统账号和环境 secret 仍需单独最小化。来自 fork 的变更不应自动获得高权限环境凭证;可先在无登录的公开样例环境跑,再由受信任分支触发带凭证路径。
矩阵不能把默认 Chromium、channel: 'chromium' 和 channel: 'chrome' 合并成一个结果。公开站点可用默认三引擎矩阵发现实现差异,再按用户占比补品牌 Chrome/Edge;企业内网站点若受浏览器策略、强制扩展或专有 codec 影响,则应把目标品牌 Stable channel 放进独立 project,并让 runner 镜像明确安装该渠道。品牌渠道可能随系统更新变化,升级作业必须先冻结版本证据,再决定基线是否更新。
Playwright 官方CI 指南建议 CI 上从 1 个 worker 起步,再根据稳定性扩展;浏览器缓存也不默认推荐,因为恢复大缓存的时间可能接近重新下载,而且 Linux 系统依赖不能随 browser cache 一起恢复。分层 CI 的指标应同时看耗时、排队、首次失败率、重试后通过率和各 project 独有缺陷数,不能只看最终绿色比例。
缓存命中之前先算磁盘与版本键
Playwright 默认把浏览器放在操作系统缓存目录,也允许用 PLAYWRIGHT_BROWSERS_PATH 指定共享或工作区外路径。缓存键至少包含操作系统、CPU 架构和 Playwright 版本;仅用 chromium 作为键会让升级后的包命中旧 revision,最终报 executable 不存在。官方浏览器文档还说明多份 Playwright client 会登记对浏览器 revision 的引用,无 client 需要的旧 revision会被回收;设置 PLAYWRIGHT_SKIP_BROWSER_GC=1 会阻止自动清理,适合只读预制镜像,却会把容量责任转交给镜像维护者。
出现 ENOSPC: no space left on device, write 时,不要只检查最终 browser cache。安装还会经历下载、临时文件和解压峰值,容器构建也可能同时保留旧 layer。可以把 PLAYWRIGHT_BROWSERS_PATH 指向受控的大容量目录,并按操作系统规则检查临时目录与镜像层;安装和运行必须使用同一个 PLAYWRIGHT_BROWSERS_PATH,否则安装成功后测试仍会报告 executable 不存在。共享目录还要确认 runner 用户有读写权限,不能用放宽全目录权限代替正确的 owner 和 ACL。
磁盘预算可以用“下载压缩包峰值 + 解压后 browser 目录 + 并发安装副本 + 保留的旧 revision”估算。CI 节点镜像若已带浏览器,再恢复一次缓存反而会重复占用。每轮作业可记录 npx playwright install --list、缓存目录大小和剩余磁盘;达到水位先停止扩容矩阵并清理无引用 revision,不要等到 report 写入阶段才 ENOSPC,因为那会产生“测试已执行但证据丢失”的更坏状态。
从启动失败反推依赖、沙箱和共享内存
browserType.launch: Executable doesn't exist 通常是包与 browser revision 不匹配,先执行 npx playwright --version、npx playwright install --list,再按 lockfile 版本安装。Host system is missing dependencies 指向 Linux 系统库或不受支持的发行版;使用官方支持的 Debian/Ubuntu 基线,或采用版本匹配的 Playwright Docker 镜像。官方Docker 指南明确说明镜像含 browser 与系统依赖,但不含项目使用的 Playwright npm 包,而且镜像版本必须与项目版本匹配。
Chromium 在容器中随机崩溃并出现共享内存相关信号时,检查容器 /dev/shm 与 --ipc=host;官方 Docker 配置把它列为 Chromium 的推荐项。以 root 运行会关闭 Chromium sandbox,访问受信任的自有 E2E 站点与抓取不受信任网页的风险完全不同。后者应使用独立用户和合适的 seccomp profile,不能为了启动成功长期授予广泛 capability。
headed 模式在 Linux 需要 Xvfb,headless 默认不需要显示服务器。若只有 headed 失败,检查 Xvfb、字体、窗口管理和 GPU 配置;若 headless 与 headed 的布局不同,固定 viewport、字体和动画后再比较。时区失败则同时打印 browser Intl.DateTimeFormat().resolvedOptions() 和 runner process.env.TZ;语言失败同时观察 navigator.language、Accept-Language 与服务端返回文案。分辨率失败要区分 viewport、screen、device scale factor 和视频尺寸,四者不是同一个字段。
清理、回滚和退出要能恢复完整基线
本地实验结束可以按当前 Playwright client 卸载浏览器;--all 会删除其他 Playwright 安装引用的浏览器,属于更大破坏面,只应在确认机器没有其他项目依赖后使用。共享 runner 不要在普通作业末尾执行全局卸载,否则并发作业可能正在使用同一缓存。
# 查看当前 client 识别到的浏览器
npx playwright install --list
# 只移除当前 Playwright 安装管理的 browser
npx playwright uninstall
# 删除当前项目测试产物;先确认路径位于工作区且没有保留要求
$root = (Get-Location).Path
foreach ($item in @('.\test-results', '.\playwright-report')) {
$target = [IO.Path]::GetFullPath((Join-Path $root $item))
if (-not $target.StartsWith($root + [IO.Path]::DirectorySeparatorChar)) {
throw "拒绝清理工作区外路径: $target"
}
if (Test-Path -LiteralPath $target) {
Remove-Item -LiteralPath $target -Recurse -Force
}
if (Test-Path -LiteralPath $target) {
throw "清理未完成: $target"
}
}升级回滚不能只把 npm 包降级。需要一起恢复 lockfile、Playwright 配置、Docker 镜像 tag、browser cache key 和视觉基线;然后重新安装与旧包匹配的 browser revision,执行固定正反样例与关键路径。若新版本已经生成了不同格式的报告或截图基线,不要让旧版直接复用未经验证的新产物。
退出 Playwright 矩阵时也要保留可替代能力:谁负责三浏览器真实兼容验证,旧 artifact 何时删除,测试账号怎样吊销,CI secret 怎样撤回,镜像与 cache 怎样清理,导航或发布门禁怎样移除。只删除配置会留下浏览器缓存、账号、对象存储附件和误导性的绿色徽标。
底层运行模型决定并发和失败传播
Playwright Test 先读取配置,把测试集合与 projects 做笛卡尔展开,再由 worker process 执行测试文件。每个 project 选择 browser type 和 context options;测试 fixture 创建隔离 context 与 page。context 是 cookie、localStorage、权限和网络状态的主要隔离单元,比每个用例启动一个完整 browser 进程便宜。测试失败后 worker 可能被丢弃并重启,新的 worker 继续获得相同 parallelIndex,但进程内单例、临时连接和未提交内存状态已经不存在。
这解释了几个常见现象。全局变量保存的登录态在失败重启后消失;共享外部账号却仍然被上一次尝试占用。project 数量增加不代表一定同时启动相同数量 browser,实际并发受全局 workers、project workers、文件调度和 CI shard 共同限制。fullyParallel 会进一步把同一文件内测试并行化,如果用例共享页面、下载目录或数据,就会放大竞争。
项目依赖也会改变执行图。setup project 通过后,Chromium、Firefox、WebKit 才会并行;setup 失败时依赖 project 不运行。官方 Projects 文档说明 reporter 和 trace 能展示 setup 测试,因此登录或种子数据初始化最好做成可见的 setup project,而不是藏在一个没有步骤证据的全局脚本里。teardown 在依赖 project 结束后运行,但节点被强制取消仍可能来不及执行,所以外部资源最终还需要 TTL 清扫。
架构选型要用风险、容量和责任作答
只支持企业受管 Chrome 的内部系统,可以把 Chromium 构建作为每次提交主门禁,再用 channel: 'chrome' 覆盖品牌渠道和企业策略影响;Firefox/WebKit 用于共享组件和外部入口。面向公众的产品则应让真实用户占比、合同支持矩阵和关键业务路径决定浏览器层级。WebKit project 是高价值的早期信号,macOS 上运行 WebKit 通常比 Linux 更接近 Safari 的平台行为,但 Playwright 仍不驱动品牌 Safari;若发布承诺包含 Safari,门禁中必须另有真实 Safari 或设备实验室结果。品牌 Chrome channel 与默认 Chromium 构建也应分别记录,不能合并成一个“Chromium 系通过”。
容量上,粗略成本是“测试时长 × project 数 × 环境组合 ÷ 有效并发”,再加 browser 下载、镜像拉取、视频/trace 和失败重试。把 worker 从 2 提到 16 可能缩短浏览器步骤,却把被测服务、账号池和数据库种子压垮,最终得到更多 flaky。扩并发前观察服务端限流、CPU、内存、连接池、测试账号可用数与 artifact 增长率,按最窄资源设上限。
权限上,browser runner 只需要访问测试环境和必要 API;不要给生产域名、生产 cookie、云管理权限或任意出网。凭证按环境与角色拆分,短期注入,日志只显示变量名和脱敏标识。敏感数据优先在生成前替换成合成数据,不能指望截图或 trace 生成后再完整清洗,因为网络 body、DOM snapshot、console 和 storage 可能各存一份。
长期治理至少保留五个 owner:浏览器与镜像版本 owner、测试框架与 fixtures owner、业务关键路径 owner、测试账号与数据 owner、CI artifact 与成本 owner。升级先在独立分支更新包与 browser revision,跑固定正反样例,再跑关键路径和视觉基线;浏览器跳过必须记录问题、影响、临时期限和恢复条件。重试后通过率持续上升时,先治理 flaky 根因,不要继续增加重试。
当团队能从一个红色 project 反推出 browser revision、context 环境、测试 attempt、网络与页面证据,并能说明为什么该 project 位于当前 CI 层级,三浏览器矩阵才算真正建立。它不是兼容性数量游戏,而是一套把用户风险、执行成本和修复责任连接起来的工程模型。
