用 Playwright 的 projects 配置多浏览器多环境:一份配置跑出多套组合
先说触发这篇的实际处境:一套端到端用例写好了,接下来要求就开始叠加——本地只想跑 chromium 图快,CI 上三个内核都要过;staging 环境网络抖,失败了想自动重试几次,生产环境的冒烟用例反而一次都不许重试;还有一小撮 smoke 用例希望单独拎出来先跑。
最容易走上的岔路是复制配置文件:playwright.staging.config.ts、playwright.smoke.config.ts,然后靠 --config 挑一份。文件一多,改一个超时要改四个地方。Playwright 的配置里有一个 projects 数组,就是用来收编这类组合的。
一、projects 到底是什么
仓库文档 docs/src/test-projects-js.md 开头给的定义很直白:project 是一组用同一套配置运行的测试的逻辑分组。用它可以在不同浏览器和设备上跑,也可以把同一批用例跑在不同配置下——文档里举的例子是登录态与未登录态两种状态。
更关键的一句在 API 文档 docs/src/test-api/class-testproject.md 里:TestProject 的所有属性在顶层 TestConfig 里同样可用,写在顶层时这些属性由所有 project 共享。这句决定了配置该怎么组织——公共的写顶层,分歧的写进各个 project。文档自己的示例就是这么摆的:timeout 和 use.ignoreHTTPSErrors 放顶层,浏览器差异放 projects。
二、前置条件:这是 JS/TS 绑定的能力
这一段别跳。docs/src/test-api/class-testproject.md 的文件头写着 langs: js,class: TestProject 标注 since: v1.10。也就是说,projects 属于 Playwright Test Runner 的 JS/TS 绑定,配置写在 playwright.config.ts 里。Python、Java、C# 绑定有没有等价的配置结构,我们在这个文件里没有找到对应说明,别把这里的写法照搬过去。
依赖上,配置文件里从 @playwright/test 导入 defineConfig,需要预置设备时再导入 devices:
import { defineConfig, devices } from '@playwright/test';
几个字段的引入版本,文档里逐条标了 since:dependencies 是 since: v1.31,teardown 是 since: v1.34,project 级的 workers 是 since: v1.52。手上的 Playwright 比这些老,对应字段就不存在。
三、三种典型摆法
多浏览器
docs/src/test-projects-js.md 的第一个示例,是把内核和设备各写成一个 project:
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'Microsoft Edge',
use: {
...devices['Desktop Edge'],
channel: 'msedge'
},
},
]
这里改的是 use:把设备描述展开进去,再叠加自己的选项。品牌浏览器多一个 channel。文档同时提到,可用的设备参数在仓库的 packages/isomorphic/deviceDescriptorsSource.json 注册表里,具体有哪些条目以仓库最新内容为准,本文不罗列。
多环境
同一份文档的第二个示例把 timeout 提到了顶层,注释直接写明它在所有测试之间共享,而 baseURL 与 retries 按环境分开:
export default defineConfig({
timeout: 60000, // Timeout is shared between all tests.
projects: [
{
name: 'staging',
use: {
baseURL: 'staging.example.com',
},
retries: 2,
},
{
name: 'production',
use: {
baseURL: 'production.example.com',
},
retries: 0,
},
],
});
其中的 60000、2、0 是仓库示例里的值,不是推荐值。要提醒一句:baseURL 指向真实环境,这类配置改错方向就会把用例打到线上,评审时值得单独看一眼——这是通用做法,不是仓库里的官方要求。
按文件拆分
第三个示例用 testMatch 和 testIgnore 把用例切成两拨:Smoke 项目匹配 /.*smoke.spec.ts/ 且 retries: 0,Default 项目用 testIgnore 排掉同一批文件,retries: 2。两个字段的类型在 API 文档里写明是 string | RegExp 或它们的数组,字符串按 glob 处理,匹配的是绝对文件路径。
四、dependencies:让 setup 先跑完
dependencies 是一个项目名数组,语义是「这些项目里的测试跑完之后,本项目才开始」。文档给的典型用法是把全局准备动作写成一个 project:
projects: [
{
name: 'setup',
testMatch: '**/*.setup.ts',
},
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
dependencies: ['setup'],
},
]
运行顺序在文档的 Running Sequence 一节写得很清楚:先跑 setup 项目的测试,全部通过后,依赖它的项目才开始,这些项目之间默认并行(受最大 worker 数限制)。如果有多个依赖,这些依赖之间也是并行跑;只要其中一个依赖的测试失败,依赖它的项目就整个不跑。
选择 dependencies 而不是 globalSetup 的差别,docs/src/test-global-setup-teardown-js.md 开头列了一张对照表:项目依赖的方式下,setup 会作为独立项目出现在 HTML 报告里、可以录 trace、可以用 fixture,而 globalSetup 这几项表格里标的是不支持。文档把项目依赖标为推荐做法。
收尾用 teardown:它写在 setup 项目上,值是另一个项目的名字,在本项目及所有依赖它的项目跑完之后执行。API 文档给的模式是 setup 项目带 teardown: 'teardown',另有一个 teardown 项目用 testMatch: /global.teardown\.ts/ 匹配收尾脚本。
五、几个不那么常被翻到的字段
除了上面用到的,docs/src/test-api/class-testproject.md 里还有几个字段值得单独记一下:
| 字段 | 文档写明的语义 |
|---|---|
workers | 本项目并行 worker 进程数上限,也可以写成逻辑 CPU 核心数的百分比 |
repeatEach | 每个用例重复运行的次数 |
respectGitIgnore | 搜索测试文件时是否跳过 .gitignore 里的条目 |
snapshotDir | toMatchSnapshot 生成的快照文件的基准目录,相对配置文件 |
metadata | 会被原样序列化成 JSON 放进测试报告的元数据 |
这张表要配着第一节那句话读:这些字段在顶层 TestConfig 里同名可写,写在顶层就是全局的,写进某个 project 就只作用于它。workers 尤其典型——文档给的场景是某个项目下所有用例共用一个测试账号之类的单一资源,因而不能并行,把这个项目的 workers 限成 1 就能避免同时占用;文档同时说明,全局的 workers 限制作用于 worker 进程总数,而 Playwright 会再按项目级的值把该项目用到的 worker 数压下去。也就是说项目级的值只能更严,不能突破全局上限。
六、边界:这些地方文档写了但容易漏
name不能是动态值。API 文档在name属性下挂了一个 warning:Playwright 会多次执行配置文件,不要在配置里动态产生不稳定的值。用时间戳当项目名,正是踩这一条。--no-deps同时关掉 teardown。文档在dependencies和teardown两处都写明,传--no-deps会当作没配过它们。- 过滤条件不会拦住依赖。
--grep/--grep-invert、--shard、命令行里按位置过滤、test.only(),选中的都是主测试;一旦这些测试属于某个有依赖的项目,依赖项目里的全部测试都会跟着跑。想只跑选中的项目,得显式加--no-deps。 - 默认值会随版本变。
testMatch的默认 glob 是**/*.@(spec|test).?(c|m)[jt]s?(x),timeout默认 30 秒,outputDir默认是<package.json-directory>/test-results且在开始时被清理,project 级workers默认没有上限——这些是仓库当前文档里的默认值,随版本可能变动。 - 参数化 project 的行为变过。
docs/src/test-parameterize-js.md在参数化项目一节挂了一条 note,写明该行为在 1.18 版发生过变化。老配置迁上来时值得注意。 - VS Code 插件默认只跑 Chrome。
docs/src/test-projects-js.md写明 VS Code test runner 默认跑在 Chrome 上,要跑其它或多个浏览器得在测试侧边栏改 profile。本地看着只跑了一个内核、CI 上却跑三个,多半是这个原因,不是配置没生效。 - experimental / deprecated 的标注:这几个字段在
docs/src/test-api/class-testproject.md里我们没有看到相关标记。
七、怎么验证配对了
先不跑,只看收集结果:
npx playwright test --list
--list 在 docs/src/test-cli-js.md 的选项表里写明是「收集所有测试并报告,但不运行」。项目名会带在输出里——name 属性的说明就是「在报告和测试执行过程中可见」。
再按项目跑:
npx playwright test --project=firefox
选项表里 --project <project-name...> 的描述写明它接受一个项目列表、支持 * 通配、默认跑全部项目。同一张表在 Non-option arguments 那一行提醒过,$ 或 * 这类特殊符号要转义,很多 shell 和终端里还需要给参数加引号——用通配符筛项目时,这条同样值得记着。
Windows 侧的差别主要在环境变量。如果你按 docs/src/test-parameterize-js.md 的做法用环境变量切环境,文档给了三种 shell 的写法,是分开列的:
STAGING=1 npx playwright test
CMD 下要写成两句:
set STAGING=1
npx playwright test
PowerShell 下是:
$env:STAGING=1
npx playwright test
以上命令均按仓库文档中的参数语义组合,未经实测,以仓库最新内容与 --help 的实际输出为准。配置文件那侧读的是 process.env.STAGING,文档示例里用它在两个 baseURL 之间三元切换。
最后提一句本文没有覆盖的:projects 还能用来做自定义参数化,即把 option: true 的 fixture 在不同 project 里赋不同值,这属于 docs/src/test-parameterize-js.md 的范围。另外 Playwright 会启动真实浏览器并执行页面脚本,setup 项目里做的登录之类动作产生的是真实会话,别把它当成沙箱。
本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测,
因此不涉及运行速度、稳定性与实际表现的任何描述。
该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。