并行测试
简介
Playwright Test 以并行方式运行测试。为了实现这一点,它会同时运行多个工作进程。默认情况下,测试文件是并行运行的。单个文件中的测试会按顺序在同一个工作进程中运行。
- 你可以使用
test.describe.configure
来配置 单个文件中的测试 并行运行。 - 你可以通过 testProject.fullyParallel 或 testConfig.fullyParallel 配置 整个项目 中的所有测试文件并行运行。
- 要 禁用 并行,可以将 工作进程数限制为 1。
你可以控制 并行工作进程数 和 限制整个测试套件中的失败次数 以提高效率。
工作进程
所有测试都在工作进程中运行。这些进程是操作系统进程,由测试运行器协调,彼此独立运行。所有工作进程具有相同的环境,每个进程都会启动自己的浏览器。
工作进程之间无法通信。Playwright Test 会尽可能复用同一个工作进程以加快测试速度,因此多个测试文件通常会在同一个工作进程中依次运行。
在 测试失败 后,工作进程总是会被关闭,以确保后续测试运行在全新的环境中。
限制工作进程数
从命令行设置:
npx playwright test --workers 4
在配置文件中设置:
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 进程中执行的,不能共享任何状态或全局变量。每个测试都会为自己执行所有相关的钩子,包括 beforeAll
和 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 }) => { /* ... */ });
或者,你也可以在配置文件中将所有测试选择加入这种完全并行模式:
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
});
你也可以仅为特定项目启用完全并行模式:
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
在配置文件中设置:
import { defineConfig } from '@playwright/test';
export default defineConfig({
// 在 CI 上限制失败次数以节省资源
maxFailures: process.env.CI ? 10 : undefined,
});
工作进程索引与并行索引
每个工作进程都被分配了两个 ID:一个从 1 开始的唯一工作进程索引(worker index),以及一个介于 0
到 workers - 1
之间的并行索引(parallel index)。当工作进程重启时(例如失败后),新的工作进程会保持相同的 parallelIndex
并获得一个新的 workerIndex
。
您可以从环境变量 process.env.TEST_WORKER_INDEX
和 process.env.TEST_PARALLEL_INDEX
中读取这些索引,或者通过 testInfo.workerIndex 和 testInfo.parallelIndex 访问它们。
在并行工作器之间隔离测试数据
你可以利用 process.env.TEST_WORKER_INDEX
或上文提到的 testInfo.workerIndex 来隔离不同工作器上运行的测试在数据库中的用户数据。同一个工作器运行的所有测试会复用相同的用户。
创建 playwright/fixtures.ts
文件,该文件将 创建 dbUserName
fixture 并在测试数据库中初始化一个新用户。使用 testInfo.workerIndex 来区分不同工作器。
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
。
// 重要:导入我们的 fixtures
import { test, expect } from '../playwright/fixtures';
test('test', async ({ dbUserName }) => {
// 在测试中使用用户名
});
控制测试顺序
Playwright Test 默认按照声明顺序执行单个文件中的测试,除非你在单个文件中并行化测试。
对于跨文件的测试执行顺序,Playwright Test 不提供任何保证,因为默认情况下测试文件是并行运行的。不过,如果你禁用并行执行,可以通过以下两种方式控制测试顺序:按字母顺序命名文件或使用"测试列表"文件。
按字母顺序排列测试文件
当你禁用并行测试执行时,Playwright Test 会按照字母顺序运行测试文件。你可以使用特定的命名约定来控制测试顺序,例如 001-user-signin-flow.spec.ts
、002-create-new-document.spec.ts
等。
使用"测试列表"文件
测试列表功能不推荐使用,仅提供尽力而为的支持。某些功能如 VS Code 扩展和追踪可能与测试列表配合不佳。
您可以将测试放在多个文件的辅助函数中。参考以下示例,测试不是直接在文件中定义,而是封装在一个包装函数中。
import { test, expect } from '@playwright/test';
export default function createTests() {
test('feature-a 示例测试', async ({ page }) => {
// ... 测试内容
});
}
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()
调用只会影响单个文件中的测试。
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 来禁用并行执行,并指定您的测试列表文件。
import { defineConfig } from '@playwright/test';
export default defineConfig({
workers: 1,
testMatch: 'test.list.ts',
});
不要在辅助文件中直接定义测试。这可能导致意外结果,因为您的测试现在依赖于 import
/require
语句的顺序。应该像上面示例那样,将测试包装在函数中,由测试列表文件显式调用。