Skip to main content

并行性

介绍

Introduction

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

Playwright Test runs tests in parallel. In order to achieve that, it runs several worker processes that run at the same time. By default, test files are run in parallel. Tests in a single file are run in order, in the same worker process.

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

You can control the number of parallel worker processes and limit the number of failures in the whole test suite for efficiency.

工作进程

Worker processes

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

All tests run in worker processes. These processes are OS processes, running independently, orchestrated by the test runner. All workers have identical environments and each starts its own browser.

工作线程之间无法沟通。Playwright Test 尽可能地重用单个 Worker 来提高测试速度,因此多个测试文件通常在单个 Worker 中依次运行。

You can't communicate between the workers. Playwright Test reuses a single worker as much as it can to make testing faster, so multiple test files are usually run in a single worker one after another.

工作线程在 测试失败 后总是关闭,以保证后续测试的原始环境。

Workers are always shutdown after a test failure to guarantee pristine environment for following tests.

限制工作线程

Limit workers

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

You can control the maximum number of parallel worker processes via command line or in the configuration file.

从命令行:

From the command line:

npx playwright test --workers 4

在配置文件中:

In the configuration file:

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,
});

禁用并行性

Disable parallelism

你可以通过随时只允许一个工作线程来禁用任何并行性。在配置文件中设置 workers: 1 选项或将 --workers=1 传递到命令行。

You can disable any parallelism by allowing just a single worker at any time. Either set workers: 1 option in the configuration file or pass --workers=1 to the command line.

npx playwright test --workers=1

在单个文件中并行测试

Parallelize tests in a single file

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

By default, tests in a single file are run in order. If you have many independent tests in a single file, you might want to run them in parallel with test.describe.configure().

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

Note that parallel tests are executed in separate worker processes and cannot share any state or global variables. Each test executes all relevant hooks just for itself, including beforeAll and afterAll.

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

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

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

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

Alternatively, you can opt-in all tests into this fully-parallel mode in the configuration file:

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

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

你还可以为几个项目选择完全并行模式:

You can also opt in for fully-parallel mode for just a few projects:

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,
},
]
});

串行模式

Serial mode

你可以将相互依赖的测试注释为串行。如果其中一项串行测试失败,则跳过所有后续测试。一组中的所有测试都会一起重试。

You can annotate inter-dependent tests as serial. If one of the serial tests fails, all subsequent tests are skipped. All tests in a group are retried together.

注意

不建议使用串口。通常最好将测试隔离起来,这样它们就可以独立运行。

Using serial is not recommended. It is usually better to make your tests isolated, so they can be run independently.

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.nodejs.cn/');
});

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

多台机器之间的分片测试

Shard tests between multiple machines

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

Playwright Test can shard a test suite, so that it can be executed on multiple machines. See sharding guide for more details.

npx playwright test --shard=2/3

限制失败并快速失败

Limit failures and fail fast

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

You can limit the number of failed tests in the whole test suite by setting maxFailures config option or passing --max-failures command line flag.

当使用 "最大失败次数" 设置运行时,Playwright Test 将在达到失败测试次数后停止,并跳过任何尚未执行的测试。这对于避免在损坏的测试套件上浪费资源很有用。

When running with "max failures" set, Playwright Test will stop after reaching this number of failed tests and skip any tests that were not executed yet. This is useful to avoid wasting resources on broken test suites.

传递命令行选项:

Passing command line option:

npx playwright test --max-failures=10

配置文件中设置:

Setting in the configuration file:

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 index and parallel index

每个工作进程分配有两个 id:从 1 开始的唯一工作索引,以及介于 0workers - 1 之间的并行索引。当工作进程重新启动时,例如在发生故障后,新的工作进程具有相同的 parallelIndex 和新的 workerIndex

Each worker process is assigned two ids: a unique worker index that starts with 1, and a parallel index that is between 0 and workers - 1. When a worker is restarted, for example after a failure, the new worker process has the same parallelIndex and a new workerIndex.

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

You can read an index from environment variables process.env.TEST_WORKER_INDEX and process.env.TEST_PARALLEL_INDEX, or access them through testInfo.workerIndex and testInfo.parallelIndex.

隔离并行工作线程之间的测试数据

Isolate test data between parallel workers

你可以利用上面提到的 process.env.TEST_WORKER_INDEXtestInfo.workerIndex 在不同工作线程上运行的测试之间隔离数据库中的用户数据。工作线程运行的所有测试都重复使用同一用户。

You can leverage process.env.TEST_WORKER_INDEX or testInfo.workerIndex mentioned above to isolate user data in the database between tests running on different workers. All tests run by the worker reuse the same user.

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

Create playwright/fixtures.ts file that will create dbUserName fixture and initialize a new user in the test database. Use testInfo.workerIndex to differentiate between workers.

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}`;
// Inialize user in the database.
await createUserInTestDatabase(userName);
await use(userName);
// Clean up after the tests are done.
await deleteUserFromTestDatabase(userName);
}, { scope: 'worker' }],
});

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

Now, each test file should import test from our fixtures file instead of @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.
});

控制测试顺序

Control test order

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

Playwright Test runs tests from a single file in the order of declaration, unless you parallelize tests in a single file.

无法保证跨文件的测试执行顺序,因为 Playwright Test 默认并行运行测试文件。但是,如果你选择 禁用并行性,则可以通过按字母顺序命名文件或使用 "测试清单" 文件来控制测试顺序。

There is no guarantee about the order of test execution across the files, because Playwright Test runs test files in parallel by default. However, if you disable parallelism, you can control test order by either naming your files in alphabetical order or using a "test list" file.

按字母顺序对测试文件进行排序

Sort test files alphabetically

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

When you disable parallel test execution, Playwright Test runs test files in alphabetical order. You can use some naming convention to control the test order, for example 001-user-signin-flow.spec.ts, 002-create-new-document.spec.ts and so on.

使用 "测试清单" 文件

Use a "test list" file

警告

不鼓励使用测试列表,仅尽力支持测试列表。某些功能(例如 VS Code 扩展和跟踪)可能无法与测试列表一起正常工作。

Tests lists are discouraged and supported as a best-effort only. Some features such as VS Code Extension and tracing may not work properly with test lists.

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

You can put your tests in helper functions in multiple files. Consider the following example where tests are not defined directly in the file, but rather in a wrapper function.

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() 调用仅影响单个文件的测试。

You can create a test list file that will control the order of tests - first run feature-b tests, then feature-a tests. Note how each test file is wrapped in a test.describe() block that calls the function where tests are defined. This way test.use() calls only affect tests from a single file.

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);

现在,通过将工作进程设置为 1 来禁用并行执行,并指定你的测试列表文件。

Now disable parallel execution by setting workers to one, and specify your test list file.

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

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

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

Do not define your tests directly in a helper file. This could lead to unexpected results because your tests are now dependent on the order of import/require statements. Instead, wrap tests in a function that will be explicitly called by a test list file, as in the example above.