并行性
介绍
🌐 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.
- 你可以使用
test.describe.configure配置测试,以在单个文件中并行运行测试。 - 你可以配置整个项目,使所有文件中的所有测试并行运行,方法是使用 testProject.fullyParallel 或 testConfig.fullyParallel。
- 要禁用并行,请将工作进程数量限制为一个。
你可以控制整个测试套件中的并行工作进程数量,并限制失败次数以提高效率。
🌐 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 测试尽可能复用单个工作进程以加快测试速度,因此多个测试文件通常会在同一个工作进程中一个接一个地运行。
🌐 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:
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().
请注意,平行测试是在独立的工作进程中执行的,不能共享任何状态或全局变量。每个测试仅为自身执行所有相关的钩子,包括 beforeAll 和 afterAll。
🌐 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:
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
});
你还可以为几个项目选择完全并行模式:
🌐 You can also opt in for fully-parallel mode for just a few projects:
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();
});
退出完全并行模式
🌐 Opt out of fully parallel mode
如果你的配置使用 testConfig.fullyParallel 对所有测试应用并行模式,你可能仍然希望用默认设置运行某些测试。你可以在每个 describe 中覆盖模式:
🌐 If your configuration applies parallel mode to all tests using testConfig.fullyParallel, you might still want to run some tests with default settings. You can override the mode per 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 }) => {});
});
多台机器之间的分片测试
🌐 Shard tests between multiple machines
Playwright 测试可以将测试套件拆分成多个部分,以便在多台机器上执行。更多详情请参阅拆分指南。
🌐 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:
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开始的唯一工作索引,以及一个介于 0 和 workers - 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_INDEX 和 process.env.TEST_PARALLEL_INDEX 中读取索引,或者通过 testInfo.workerIndex 和 testInfo.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_INDEX 或 testInfo.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.
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。
🌐 Now, each test file should import test from our fixtures file instead of @playwright/test.
// 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 测试会按照声明顺序从单个文件中运行测试,除非你在单个文件中并行测试。
🌐 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.ts、002-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.
import { test, expect } from '@playwright/test';
export default function createTests() {
test('feature-a example test', async ({ page }) => {
// ... test goes here
});
}
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.
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);
现在通过将工作进程设置为一个来禁用并行执行,并指定你的测试列表文件。
🌐 Now disable parallel execution by setting workers to one, and specify your test list file.
import { defineConfig } from '@playwright/test';
export default defineConfig({
workers: 1,
testMatch: 'test.list.ts',
});
不要直接在辅助文件中定义你的测试。这可能导致意外结果,因为你的测试现在依赖于 import/require 语句的顺序。相反,应将测试封装在一个函数中,该函数将由测试列表文件显式调用,如上面的示例所示。