Playwright Test TestProject 配置详解:多项目编排、依赖链与并行控制的完整指南
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
本文基于 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。
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考