Playwright Test TestProject 配置详解:多项目编排、依赖链与并行控制的完整指南
本文基于 Playwright 仓库的 API 文档 docs/src/test-api/class-testproject.md 展开,系统讲解 TestProject 的全部配置项及其解析优先级,并结合 packages/playwright/src/common/config.ts 等源码实现,说明多浏览器/多设备项目如何声明、dependencies/teardown 如何构成执行链、以及 workers、fullyParallel 等并行选项在底层是如何生效的。读完后你应能独立编写可复制运行的 playwright.config.ts,并准确解释每个项目级参数的默认值与覆盖规则。
核心概念:TestProject 与 FullProject 的区别
Playwright Test 支持在一次运行中同时跑多个测试项目(project),典型场景是同一套测试在多个浏览器、桌面/移动配置下分别执行。文档 class-testproject.md 定义了配置文件里项目的写法:
TestProject描述的是配置文件中的项目格式,即你在playwright.config.ts里projects数组中写的每一项;- 运行时想访问解析后的完整配置,应使用
FullProject(见 class-fullproject.md)。
项目通过 [property: TestConfig.projects] 声明,位置是配置文件。所有 TestProject 的属性同样可以写在顶层 TestConfig 中,此时被所有项目共享。从源码看,这一"项目级覆盖顶层"的解析逻辑集中在 FullProjectInternal 构造函数,每个属性都通过 takeFirst 链取值,例如:
// packages/playwright/src/common/config.ts
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
timeout: takeFirst(configCLIOverrides.debug === 'inspector' ? 0 : undefined, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),
可以由此确认优先级顺序为:命令行参数 > 项目级配置 > 顶层配置 > 内置默认值。另外 config.ts#L132-L134 还揭示了兜底规则:如果 projects 完全没写,Playwright 会把整个顶层配置当作唯一的项目([{ ...userConfig, workers: undefined }]),即顶层写的 use、timeout 等直接生效。
多浏览器 + 多设备项目的完整示例
下面这份配置让全部测试在 Chromium、Firefox、WebKit 的桌面版和移动版上各跑一遍(继承自 class-testproject.md 的示例):
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Options shared for all projects.
timeout: 30000,
use: {
ignoreHTTPSErrors: true,
},
// Options specific to each project.
projects: [
{
name: 'chromium',
use: devices['Desktop Chrome'],
},
{
name: 'firefox',
use: devices['Desktop Firefox'],
},
{
name: 'webkit',
use: devices['Desktop Safari'],
},
{
name: 'Mobile Chrome',
use: devices['Pixel 5'],
},
{
name: 'Mobile Safari',
use: devices['iPhone 12'],
},
],
});
顶层的 timeout: 30000 和 use.ignoreHTTPSErrors 被所有项目共享;每个项目仅声明自己的 name 和 use(devices 预设来自 TestOptions 的设备描述符)。注意 FullProjectInternal 中对 use 的处理是 mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),即项目 use 会按字段合并在顶层 use 之上,而不是整体替换——这正是多项目配置能"只写差异"的原因。
项目执行顺序:dependencies 与 teardown
property: dependencies(since v1.31,type: ?Array<string>)
列出必须在本项目任何测试运行之前先跑完的项目。它最常用于把全局 setup 组织成"以测试形式存在的动作"——这样 setup 步骤能在测试报告中展示、并能产生 trace 等工件。传入 --no-deps 命令行参数可忽略依赖,行为等同于未声明(该参数在 program.ts#L225 中注册为 --no-deps)。
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /global.setup\.ts/,
},
{
name: 'chromium',
use: devices['Desktop Chrome'],
dependencies: ['setup'],
},
{
name: 'firefox',
use: devices['Desktop Firefox'],
dependencies: ['setup'],
},
{
name: 'webkit',
use: devices['Desktop Safari'],
dependencies: ['setup'],
},
],
});
从源码结构看,依赖在配置加载阶段由 resolveProjectDependencies 解析:依赖名必须能唯一匹配某个项目,否则直接抛出 Project 'xxx' depends on unknown project 'yyy' 或"依赖名不唯一"的错误,属于配置期校验而非运行期失败。
property: teardown(since v1.34,type: ?string)
指向一个在本项目及其所有依赖项目都结束之后才运行的项目名,适合做资源清理。--no-deps 同样会忽略 teardown。常见模式是 "setup + 对应 teardown":
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /global.setup\.ts/,
teardown: 'teardown',
},
{
name: 'teardown',
testMatch: /global.teardown\.ts/,
},
{
name: 'chromium',
use: devices['Desktop Chrome'],
dependencies: ['setup'],
},
{
name: 'firefox',
use: devices['Desktop Firefox'],
dependencies: ['setup'],
},
{
name: 'webkit',
use: devices['Desktop Safari'],
dependencies: ['setup'],
},
],
});
teardown 挂在 setup 项目上,意味着"当依赖 setup 的所有项目都跑完后"才执行 teardown,这与 全局 setup/teardown 文档 描述的机制相衔接,但粒度精确到项目依赖链。
测试文件选择:testDir、testMatch、testIgnore、respectGitIgnore
property: testDir(since v1.10,type: ?string)
递归扫描测试文件的目录,默认为配置文件所在目录。每个项目可以指向不同目录。示例:smoke 测试在三种浏览器上跑,其余测试只在稳定版 Chrome 上跑:
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'Smoke Chromium',
testDir: './smoke-tests',
use: { browserName: 'chromium' },
},
{
name: 'Smoke WebKit',
testDir: './smoke-tests',
use: { browserName: 'webkit' },
},
{
name: 'Smoke Firefox',
testDir: './smoke-tests',
use: { browserName: 'firefox' },
},
{
name: 'Chrome Stable',
testDir: './',
use: {
browserName: 'chromium',
channel: 'chrome',
},
},
],
});
源码中 testDir 的解析为 takeFirst(项目级 → 顶层 → configDir),testDir 同时是 snapshotDir 的默认值(见下文)。
property: testMatch(since v1.10,type: ?string | RegExp | Array)
只有匹配其中任一模式的文件才会被当作测试文件执行,匹配针对绝对文件路径进行,字符串按 glob 模式处理。默认 glob 为 **/*.@(spec|test).?(c|m)[jt]s?(x)——即带 .test 或 .spec 后缀的 JS/TS 文件,如 login-screen.wrong-credentials.spec.ts。这一默认值在 config.ts#L193 中可直接得到印证。
property: testIgnore(since v1.10,type: ?string | RegExp | Array)
与 testMatch 相反:匹配任一模式的文件不会作为测试文件执行。例如 '**/test-assets/**' 会忽略 test-assets 目录下所有文件。
property: respectGitIgnore(since v1.45,type: ?boolean)
是否在搜索测试文件时跳过 .gitignore 中的条目。默认行为(源码 config.ts#L206):当既没有显式指定顶层 testDir 也没有指定项目级 testDir 时,Playwright 会忽略匹配 .gitignore 的测试文件;该选项用于覆盖此默认行为。
并行与重复执行:fullyParallel、workers、repeatEach
property: fullyParallel(since v1.10,type: ?boolean)
Playwright Test 通过同时运行多个 worker 进程实现并行;默认并行粒度是测试文件——同一文件内的测试按顺序在同一个 worker 里执行。将本项目设为 fullyParallel: true 后,所有文件中的所有测试都会并发调度。源码中其解析优先级为命令行 → 项目 → 顶层(config.ts#L200),且 debug 模式下会被统一改写为串行(见下)。
property: workers(since v1.52,type: ?int | string)
限制本项目可用的最大并发 worker 数,也支持写成逻辑 CPU 核数的百分比,例如 '50%'。典型场景:某项目的所有测试共享一个测试账号,无法并行,把它的 workers 设为 1 即可防止并发使用共享资源。
注意:全局 [property: TestConfig.workers] 限制的是总 worker 数,而本项在总限额内进一步限制单项目占用;不设置时单项目无额外上限。
import { defineConfig } from '@playwright/test';
export default defineConfig({
workers: 10, // total workers limit
projects: [
{ name: 'runs in parallel' },
{
name: 'one at a time',
workers: 1, // workers limit for this project
},
],
});
底层解析实现在 resolveWorkers:百分比按 os.cpus().length 计算并向下取整(Math.max(1, Math.floor(cpus * percent/100))),非正数或非法值会抛出 Workers ... must be a number or percentage 错误。全局 workers 的默认值是 '50%'(config.ts#L110),另外在 debug(--debug/--pause)模式下项目级 workers 会被强制为 1(config.ts#L208-L209)。
property: repeatEach(since v1.10,type: ?int)
每个测试重复执行的次数,默认 1(config.ts#L186),用于调试不稳定(flaky)测试。可用 [property: TestConfig.repeatEach] 统一设置。
测试过滤:grep 与 grepInvert
property: grep(since v1.10,type: ?RegExp | Array<RegExp>)
只运行标题匹配任一模式的测试。正则匹配的目标字符串由以下部分按空格拼接而成:项目名、测试文件名、test.describe 名(如有)、测试名、测试 tags,例如 chromium my-test.spec.ts my-suite my-test。因此可以精确地"只在某个项目上跑某类测试"。同样可以全局设置,或通过命令行的 -g 选项传入。该选项也是测试打 tag的主要手段。
property: grepInvert(since v1.10,type: ?RegExp | Array<RegExp>)
与 grep 相反:只运行标题不匹配任一模式的测试。对应命令行选项为 --grep-invert,同样适合配合 tag 机制 排除某些测试。
断言与快照:expect、ignoreSnapshots、snapshotDir、snapshotPathTemplate、outputDir
property: expect(since v1.10,type: ?Object)
expect 断言库的项目级配置,可用 [property: TestConfig.expect] 全局设置。各字段及默认值:
| 字段 | 类型 / 默认值 | 说明 |
|---|---|---|
timeout |
int,默认 5000ms | 异步 expect 匹配器的默认超时(毫秒) |
toHaveScreenshot |
Object | [method: PageAssertions.toHaveScreenshot#1] 的配置 |
toMatchAriaSnapshot |
Object | [method: LocatorAssertions.toMatchAriaSnapshot#2] 的配置 |
toMatchSnapshot |
Object | [method: SnapshotAssertions.toMatchSnapshot#1] 的配置 |
toPass |
Object | expect(value).toPass() 的配置 |
expect.toHaveScreenshot 子项:
| 字段 | 类型 / 默认值 | 说明 |
|---|---|---|
threshold |
float | 同一像素可接受的感知色差,0(严格)到 1(宽松);"pixelmatch" 比较器在 YIQ 色彩空间计算色差,默认 0.2 |
maxDiffPixels |
int | 允许的最大差异像素数,默认未设置 |
maxDiffPixelRatio |
float | 允许的差异像素占比(0~1),默认未设置 |
animations |
"allow" | "disabled" |
见 [method: Page.screenshot] 的 animations,默认 "disabled" |
caret |
"hide" | "initial" |
见 caret,默认 "hide" |
scale |
"css" | "device" |
见 scale,默认 "css" |
stylePath |
string | Array<string> | 额外注入的样式表,见 Page.screenshot.style |
pathTemplate |
string | 控制截图存放位置的模板,语义同 [property: TestProject.snapshotPathTemplate] |
timeout |
int | 该断言的超时,默认取全局 expect timeout;设为 0 表示禁用超时 |
expect.toMatchAriaSnapshot 子项:pathTemplate(aria 快照位置模板)、children("contain" | "equal" | "deep-equal",控制快照根的子节点如何与真实可访问性树匹配,等价于在每份 aria 快照模板顶部加一个 /children 属性,单份快照可用显式 /children 覆盖)。
expect.toMatchSnapshot 子项:threshold、maxDiffPixels、maxDiffPixelRatio,语义同上。
expect.toPass 子项:timeout(毫秒)、intervals(探测间隔数组,毫秒)。
源码层面,FullProjectInternal 中 expect 按 takeFirst(projectConfig.expect, config.expect, {}) 取整个对象;若配置了 expect.toHaveScreenshot.stylePath,会被解析为相对 configDir 的绝对路径。
property: ignoreSnapshots(since v1.44,type: ?boolean)
跳过快照类断言(toMatchSnapshot()、toHaveScreenshot())。示例:只让 Chromium 项目做截图断言:
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: devices['Desktop Chrome'] },
{ name: 'firefox', use: devices['Desktop Firefox'], ignoreSnapshots: true },
{ name: 'webkit', use: devices['Desktop Safari'], ignoreSnapshots: true },
],
});
解析链为 CLI → 项目 → 顶层 → false(config.ts#L198),因此也可以传 --ignore-snapshots 全局生效。
property: snapshotDir(since v1.10,type: ?string)
toMatchSnapshot 创建快照文件的基目录(相对配置文件),默认是 [property: TestProject.testDir](config.ts#L191)。每个测试文件有独立快照目录,可通过 [property: TestInfo.snapshotDir]、[method: TestInfo.snapshotPath] 访问。例如 snapshotDir: 'snapshots' 时,测试文件 a.spec.js 的快照目录解析为 snapshots/a.spec.js-snapshots。
property: snapshotPathTemplate(since v1.28)
该属性与顶层 TestConfig.snapshotPathTemplate 共用同一说明(文档通过 include 引入,见 class-testconfig.md),用于模板化地控制快照/截图/aria 快照文件的位置,支持 {arg} 占位。项目级值优先于顶层(config.ts#L172)。快照路径的最终解析顺序可以在 testInfo.ts#L611-L616 中确认:expect.toHaveScreenshot.pathTemplate → 项目/顶层 snapshotPathTemplate → 内置旧版模板,aria 快照同理。
property: outputDir(since v1.10,type: ?string)
测试执行期间产生的文件(截图、视频、trace 等)的输出目录,默认为 <package.json 目录>/test-results(config.ts#L183)。该目录在运行开始时被清理;每次运行测试会在其中创建唯一子目录,保证并行测试互不冲突,可通过 [property: TestInfo.outputDir]、[method: TestInfo.outputPath] 访问:
import { test, expect } from '@playwright/test';
import fs from 'fs';
test('example test', async ({}, testInfo) => {
const file = testInfo.outputPath('temporary-file.txt');
await fs.promises.writeFile(file, 'Put some data to the file', 'utf8');
});
超时、重试、命名与元数据
property: timeout(since v1.10,type: ?int)
每个测试的超时,默认 30 秒。它是所有测试的基础超时:单个测试可用 [method: Test.setTimeout] 覆盖,文件/组级可用 [method: Test.describe.configure] 覆盖;顶层用 [property: TestConfig.timeout] 统一设置。
property: retries(since v1.10,type: ?int)
失败测试的最大重试次数,默认 0(config.ts#L187),更多机制见测试重试。可用 [method: Test.describe.configure] 对特定文件/组调整,或 [property: TestConfig.retries] 全局设置。
property: name(since v1.10,type: ?string)
项目名会显示在报告和运行过程中。文档特别警告:Playwright 会多次执行配置文件,不要在配置中动态生成不稳定值(比如每次运行都不同的随机 ID)。(从源码看 config.ts#L140-L154 还会在重名时给项目 id 追加数字后缀以保证唯一性。)
property: metadata(since v1.10,type: ?Metadata)
以 JSON 序列化形式直接写入测试报告的元数据,便于在 HTML 报告等展示环境信息。
项目选项汇总与解析优先级速查
| 属性 | 类型 | 默认值 | 引入版本 |
|---|---|---|---|
dependencies |
?Array<string> | 无依赖 | v1.31 |
expect |
?Object | {} |
v1.10 |
fullyParallel |
?boolean | false | v1.10 |
grep / grepInvert |
?RegExp | Array<RegExp> | 全部通过 / 不取反 | v1.10 |
ignoreSnapshots |
?boolean | false | v1.44 |
metadata |
?Metadata | {} |
v1.10 |
name |
?string | 空 | v1.10 |
outputDir |
?string | <package.json 目录>/test-results |
v1.10 |
repeatEach |
?int | 1 | v1.10 |
respectGitIgnore |
?boolean | 未显式指定 testDir 时为 true | v1.45 |
retries |
?int | 0 | v1.10 |
teardown |
?string | 无 | v1.34 |
testDir |
?string | 配置文件目录 | v1.10 |
testIgnore |
?string | RegExp | Array | [] |
v1.10 |
testMatch |
?string | RegExp | Array | **/*.@(spec|test).?(c|m)[jt]s?(x) |
v1.10 |
timeout |
?int | 30000 ms | v1.10 |
use |
?TestOptions | 合并自顶层 use |
v1.10 |
workers |
?int | string | 无项目级上限(全局默认 '50%') |
v1.52 |
结合 config.ts 的 takeFirst 链可以确认统一规则:命令行覆盖 > 项目配置 > 顶层 TestConfig > 内置默认,其中 use 是唯一按字段深度合并的选项。掌握这张表和优先级,就覆盖了 TestProject 在配置、过滤、快照、并行四个维度的全部控制点;项目间如何共享 fixture 与选项继承,可继续阅读 test-configuration.md 与 test-use-options-js.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00