跳到主要内容

并行

介绍

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

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

Worker 进程

所有测试都在 worker 进程中运行。这些进程是操作系统进程,独立运行,由测试运行器编排。所有 worker 都拥有相同的环境,并且每个都会启动自己的浏览器。

worker 之间无法相互通信。Playwright Test 会尽可能地重用单个 worker 以加快测试速度,因此通常多个测试文件会在同一个 worker 中一个接一个地运行。

worker 在 测试失败 后总是会被关闭,以确保后续测试拥有全新的环境。

限制 worker 数量

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

通过命令行

npx playwright test --workers 4

在配置文件中

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

export default defineConfig({
// Limit the number of workers on CI, use default locally
workers: process.env.CI ? 2 : undefined,
});

禁用并行

你可以通过在任何时候只允许一个 worker 来禁用并行。可以在配置文件中设置 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({
// runs all tests in all files of a specific project in parallel
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
fullyParallel: true,
},
]
});

串行模式

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

注意

不建议使用串行模式。通常最好让你的测试相互隔离,这样它们就可以独立运行。

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

// Annotate entire file as serial.
test.describe.configure({ mode: 'serial' });

let page: Page;

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

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

test('runs first', async () => {
await page.goto('https://playwright.net.cn/');
});

test('runs second', async () => {
await page.getByText('Get Started').click();
});

退出完全并行模式

如果你的配置使用 testConfig.fullyParallel 将并行模式应用于所有测试,你可能仍希望以默认设置运行某些测试。你可以在每个 describe 中覆盖此模式

test.describe('runs in parallel with other describes', () => {
test.describe.configure({ mode: 'default' });
test('in order 1', async ({ page }) => {});
test('in order 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({
// Limit the number of failures on CI to save resources
maxFailures: process.env.CI ? 10 : undefined,
});

Worker 索引和并行索引

每个 worker 进程会被分配两个 ID:一个从 1 开始的唯一 worker 索引,以及一个介于 0workers - 1 之间的并行索引。当 worker 重启时(例如在失败后),新的 worker 进程会拥有相同的 parallelIndex 和新的 workerIndex

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

隔离并行 worker 之间的测试数据

你可以利用上面提到的 process.env.TEST_WORKER_INDEXtestInfo.workerIndex 来隔离在不同 worker 上运行的测试之间数据库中的用户数据。由同一个 worker 运行的所有测试会重用同一个用户。

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

playwright/fixtures.ts
import { test as baseTest, expect } from '@playwright/test';
// Import project utils for managing users in the test database.
import { createUserInTestDatabase, deleteUserFromTestDatabase } from './my-db-utils';

export * from '@playwright/test';
export const test = baseTest.extend<{}, { dbUserName: string }>({
// Returns db user name unique for the worker.
dbUserName: [async ({ }, use) => {
// Use workerIndex as a unique identifier for each worker.
const userName = `user-${test.info().workerIndex}`;
// Initialize user in the database.
await createUserInTestDatabase(userName);
await use(userName);
// Clean up after the tests are done.
await deleteUserFromTestDatabase(userName);
}, { scope: 'worker' }],
});

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

tests/example.spec.ts
// Important: import our fixtures.
import { test, expect } from '../playwright/fixtures';

test('test', async ({ dbUserName }) => {
// Use the user name in the test.
});

控制测试顺序

Playwright Test 按声明顺序运行单个文件中的测试,除非你在单个文件中并行运行测试

文件之间的测试执行顺序没有保证,因为 Playwright Test 默认并行运行测试文件。但是,如果你禁用并行,你可以通过按字母顺序命名文件或使用“测试列表”文件来控制测试顺序。

按字母顺序排序测试文件

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

使用“测试列表”文件

警告

不鼓励使用测试列表,并且仅提供最大努力支持。某些功能,如 VS Code 扩展和跟踪,可能无法与测试列表正常工作。

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

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

export default function createTests() {
test('feature-a example test', async ({ page }) => {
// ... test goes here
});
}

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

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

test('feature-b example test', async ({ page }) => {
// ... test goes here
});
}

你可以创建一个测试列表文件来控制测试顺序 - 先运行 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);

现在通过将 worker 数量设置为一来**禁用并行执行**,并指定你的测试列表文件。

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

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

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