首页
/ Playwright Test TestProject 配置详解:多项目编排、依赖链与并行控制的完整指南

Playwright Test TestProject 配置详解:多项目编排、依赖链与并行控制的完整指南

2026-09-06 15:15:13作者:庞队千Virginia

本文基于 Playwright 仓库的 API 文档 docs/src/test-api/class-testproject.md 展开,系统讲解 TestProject 的全部配置项及其解析优先级,并结合 packages/playwright/src/common/config.ts 等源码实现,说明多浏览器/多设备项目如何声明、dependencies/teardown 如何构成执行链、以及 workersfullyParallel 等并行选项在底层是如何生效的。读完后你应能独立编写可复制运行的 playwright.config.ts,并准确解释每个项目级参数的默认值与覆盖规则。

核心概念:TestProject 与 FullProject 的区别

Playwright Test 支持在一次运行中同时跑多个测试项目(project),典型场景是同一套测试在多个浏览器、桌面/移动配置下分别执行。文档 class-testproject.md 定义了配置文件里项目的写法:

  • TestProject 描述的是配置文件中的项目格式,即你在 playwright.config.tsprojects 数组中写的每一项;
  • 运行时想访问解析后的完整配置,应使用 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 }]),即顶层写的 usetimeout 等直接生效。

多浏览器 + 多设备项目的完整示例

下面这份配置让全部测试在 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: 30000use.ignoreHTTPSErrors 被所有项目共享;每个项目仅声明自己的 nameusedevices 预设来自 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 允许的差异像素占比(01),默认未设置
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 子项:thresholdmaxDiffPixelsmaxDiffPixelRatio,语义同上。

expect.toPass 子项:timeout(毫秒)、intervals(探测间隔数组,毫秒)。

源码层面,FullProjectInternalexpecttakeFirst(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 → 项目 → 顶层 → falseconfig.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-resultsconfig.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.tstakeFirst 链可以确认统一规则:命令行覆盖 > 项目配置 > 顶层 TestConfig > 内置默认,其中 use 是唯一按字段深度合并的选项。掌握这张表和优先级,就覆盖了 TestProject 在配置、过滤、快照、并行四个维度的全部控制点;项目间如何共享 fixture 与选项继承,可继续阅读 test-configuration.md 与 test-use-options-js.md

登录后查看全文
热门项目推荐
相关项目推荐