跳到主要内容

并行测试

简介

Playwright Test 以并行方式运行测试。为了实现这一点,它会同时运行多个工作进程。默认情况下,测试文件是并行运行的。单个文件中的测试会按顺序在同一个工作进程中运行。

你可以控制 并行工作进程数限制整个测试套件中的失败次数 以提高效率。

工作进程

所有测试都在工作进程中运行。这些进程是操作系统进程,由测试运行器协调,彼此独立运行。所有工作进程具有相同的环境,每个进程都会启动自己的浏览器。

工作进程之间无法通信。Playwright Test 会尽可能复用同一个工作进程以加快测试速度,因此多个测试文件通常会在同一个工作进程中依次运行。

测试失败 后,工作进程总是会被关闭,以确保后续测试运行在全新的环境中。

限制工作进程数

您可以通过命令行或在配置文件中控制并行工作进程的最大数量。

从命令行设置:

npx playwright test --workers 4

在配置文件中设置:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
// 在 CI 环境中限制工作进程数,本地使用默认值
workers: process.env.CI ? 2 : undefined,
});

禁用并行执行

您可以通过限制任何时候只允许一个工作进程来完全禁用并行执行。可以在配置文件中设置 workers: 1 选项,或者在命令行中传递 --workers=1 参数。

npx playwright test --workers=1

在单个文件中并行化测试

默认情况下,单个文件中的测试是按顺序运行的。如果单个文件中有许多独立的测试,你可能希望使用 test.describe.configure() 来并行运行它们。

请注意,并行测试是在单独的 worker 进程中执行的,不能共享任何状态或全局变量。每个测试都会为自己执行所有相关的钩子,包括 beforeAllafterAll

import { test } from '@playwright/test';

test.describe.configure({ mode: 'parallel' });

test('runs in parallel 1', async ({ page }) => { /* ... */ });
test('runs in parallel 2', async ({ page }) => { /* ... */ });

或者,你也可以在配置文件中将所有测试选择加入这种完全并行模式:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
fullyParallel: true,
});

你也可以仅为特定项目启用完全并行模式:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
// 在特定项目中并行运行所有文件中的所有测试
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
fullyParallel: true,
},
]
});

串行模式

你可以将相互依赖的测试标注为串行模式。如果其中一个串行测试失败,所有后续测试都会被跳过。组内的所有测试会一起重试。

:::注意 不建议使用串行模式。通常更好的做法是让测试相互隔离,以便它们可以独立运行。 :::

import { test, type Page } from '@playwright/test';

// 将整个文件标注为串行模式
test.describe.configure({ mode: 'serial' });

let page: Page;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});

test.afterAll(async () => {
await page.close();
});

test('首先运行', async () => {
await page.goto('https://playwright.dev/');
});

test('其次运行', async () => {
await page.getByText('Get Started').click();
});

退出完全并行模式

如果你的配置使用 testConfig.fullyParallel 对所有测试应用并行模式,你可能仍希望某些测试使用默认设置运行。你可以按 describe 覆盖模式:

test.describe('与其他 describe 并行运行', () => {
test.describe.configure({ mode: 'default' });
test('顺序 1', async ({ page }) => {});
test('顺序 2', async ({ page }) => {});
});

在多台机器间分片测试

Playwright Test 可以对测试套件进行分片,以便在多台机器上执行。更多详情请参阅 分片指南

npx playwright test --shard=2/3

限制失败次数与快速失败

您可以通过设置 maxFailures 配置选项或传递 --max-failures 命令行参数来限制整个测试套件中的失败测试数量。

当设置了"最大失败次数"运行时,Playwright Test 在达到这个失败数量后会停止运行,并跳过所有尚未执行的测试。这有助于避免在损坏的测试套件上浪费资源。

通过命令行选项传递:

npx playwright test --max-failures=10

在配置文件中设置:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
// 在 CI 上限制失败次数以节省资源
maxFailures: process.env.CI ? 10 : undefined,
});

工作进程索引与并行索引

每个工作进程都被分配了两个 ID:一个从 1 开始的唯一工作进程索引(worker index),以及一个介于 0workers - 1 之间的并行索引(parallel index)。当工作进程重启时(例如失败后),新的工作进程会保持相同的 parallelIndex 并获得一个新的 workerIndex

您可以从环境变量 process.env.TEST_WORKER_INDEXprocess.env.TEST_PARALLEL_INDEX 中读取这些索引,或者通过 testInfo.workerIndextestInfo.parallelIndex 访问它们。

在并行工作器之间隔离测试数据

你可以利用 process.env.TEST_WORKER_INDEX 或上文提到的 testInfo.workerIndex 来隔离不同工作器上运行的测试在数据库中的用户数据。同一个工作器运行的所有测试会复用相同的用户。

创建 playwright/fixtures.ts 文件,该文件将 创建 dbUserName fixture 并在测试数据库中初始化一个新用户。使用 testInfo.workerIndex 来区分不同工作器。

playwright/fixtures.ts
import { test as baseTest, expect } from '@playwright/test';
// 导入项目工具函数,用于管理测试数据库中的用户
import { createUserInTestDatabase, deleteUserFromTestDatabase } from './my-db-utils';

export * from '@playwright/test';
export const test = baseTest.extend<{}, { dbUserName: string }>({
// 返回对每个工作器唯一的数据库用户名
dbUserName: [async ({ }, use) => {
// 使用 workerIndex 作为每个工作器的唯一标识符
const userName = `user-${test.info().workerIndex}`;
// 在数据库中初始化用户
await createUserInTestDatabase(userName);
await use(userName);
// 测试完成后清理
await deleteUserFromTestDatabase(userName);
}, { scope: 'worker' }],
});

现在,每个测试文件应该从我们的 fixtures 文件中导入 test 而不是 @playwright/test

tests/example.spec.ts
// 重要:导入我们的 fixtures
import { test, expect } from '../playwright/fixtures';

test('test', async ({ dbUserName }) => {
// 在测试中使用用户名
});

控制测试顺序

Playwright Test 默认按照声明顺序执行单个文件中的测试,除非你在单个文件中并行化测试

对于跨文件的测试执行顺序,Playwright Test 不提供任何保证,因为默认情况下测试文件是并行运行的。不过,如果你禁用并行执行,可以通过以下两种方式控制测试顺序:按字母顺序命名文件或使用"测试列表"文件。

按字母顺序排列测试文件

当你禁用并行测试执行时,Playwright Test 会按照字母顺序运行测试文件。你可以使用特定的命名约定来控制测试顺序,例如 001-user-signin-flow.spec.ts002-create-new-document.spec.ts 等。

使用"测试列表"文件

注意

测试列表功能不推荐使用,仅提供尽力而为的支持。某些功能如 VS Code 扩展和追踪可能与测试列表配合不佳。

您可以将测试放在多个文件的辅助函数中。参考以下示例,测试不是直接在文件中定义,而是封装在一个包装函数中。

feature-a.spec.ts
import { test, expect } from '@playwright/test';

export default function createTests() {
test('feature-a 示例测试', async ({ page }) => {
// ... 测试内容
});
}

feature-b.spec.ts
import { test, expect } from '@playwright/test';

export default function createTests() {
test.use({ viewport: { width: 500, height: 500 } });

test('feature-b 示例测试', async ({ page }) => {
// ... 测试内容
});
}

您可以创建一个测试列表文件来控制测试执行顺序 - 先运行 feature-b 测试,再运行 feature-a 测试。注意每个测试文件都被包裹在调用测试定义函数的 test.describe() 块中。这样 test.use() 调用只会影响单个文件中的测试。

test.list.ts
import { test } from '@playwright/test';
import featureBTests from './feature-b.spec.ts';
import featureATests from './feature-a.spec.ts';

test.describe(featureBTests);
test.describe(featureATests);

现在通过将 workers 设置为 1 来禁用并行执行,并指定您的测试列表文件。

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
workers: 1,
testMatch: 'test.list.ts',
});
备注

不要在辅助文件中直接定义测试。这可能导致意外结果,因为您的测试现在依赖于 import/require 语句的顺序。应该像上面示例那样,将测试包装在函数中,由测试列表文件显式调用。