跳到主要内容

重试机制

简介

测试重试是一种在测试失败时自动重新运行测试的方法。当测试不稳定并且偶尔失败时,这非常有用。测试重试可以在配置文件中进行配置。

失败处理

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

请参考以下代码片段:

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

test.describe('套件', () => {
test.beforeAll(async () => { /* ... */ });
test('第一个正常', async ({ page }) => { /* ... */ });
test('第二个不稳定', async ({ page }) => { /* ... */ });
test('第三个正常', async ({ page }) => { /* ... */ });
test.afterAll(async () => { /* ... */ });
});

所有测试通过时,它们会按顺序在同一个 worker 进程中运行。

  • Worker 进程启动
    • 执行 beforeAll 钩子
    • 第一个正常 通过
    • 第二个不稳定 通过
    • 第三个正常 通过
    • 执行 afterAll 钩子

如果有任何测试失败,Playwright Test 会丢弃整个 worker 进程及其浏览器,并启动一个新的 worker 进程。测试将在新的 worker 进程中从下一个测试继续执行。

  • Worker 进程 #1 启动
    • 执行 beforeAll 钩子
    • 第一个正常 通过
    • 第二个不稳定 失败
    • 执行 afterAll 钩子
  • Worker 进程 #2 启动
    • 再次执行 beforeAll 钩子
    • 第三个正常 通过
    • 执行 afterAll 钩子

如果你启用了重试机制,第二个 worker 进程会从重试失败的测试开始,并从那里继续执行。

  • Worker 进程 #1 启动
    • 执行 beforeAll 钩子
    • 第一个正常 通过
    • 第二个不稳定 失败
    • 执行 afterAll 钩子
  • Worker 进程 #2 启动
    • 再次执行 beforeAll 钩子
    • 第二个不稳定 被重试并通过
    • 第三个正常 通过
    • 执行 afterAll 钩子

这种方案非常适用于相互独立的测试,并保证失败的测试不会影响健康的测试。

重试机制

Playwright 支持测试重试。启用后,失败的测试会被多次重试直到通过,或者达到最大重试次数为止。默认情况下,失败的测试不会被重试。

# 让失败的测试最多重试 3 次
npx playwright test --retries=3

你可以在配置文件中配置重试次数:

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

export default defineConfig({
// 让失败的测试最多重试 3 次
retries: 3,
});

Playwright Test 会将测试分为以下几类:

  • "通过" - 首次运行就通过的测试;
  • "不稳定" - 首次运行失败,但重试后通过的测试;
  • "失败" - 首次运行失败且所有重试都失败的测试。
使用 1 个 worker 运行 3 个测试

✓ example.spec.ts:4:2 › 第一个通过 (438ms)
x example.spec.ts:5:2 › 第二个不稳定 (691ms)
✓ example.spec.ts:5:2 › 第二个不稳定 (522ms)
✓ example.spec.ts:6:2 › 第三个通过 (932ms)

1 个不稳定
example.spec.ts:5:2 › 第二个不稳定
2 个通过 (4s)

你可以通过 testInfo.retry 在运行时检测是否为重试,在任何测试、钩子或 fixture 中都可以访问。下面是一个在重试前清理服务器端状态的示例。

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

test('我的测试', async ({ page }, testInfo) => {
if (testInfo.retry)
await cleanSomeCachesOnTheServer();
// ...
});

你可以通过 test.describe.configure() 为特定的测试组或单个文件指定重试次数。

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

test.describe(() => {
// 此 describe 分组下的所有测试都会有 2 次重试机会。
test.describe.configure({ retries: 2 });

test('测试 1', async ({ page }) => {
// ...
});

test('测试 2', async ({ page }) => {
// ...
});
});

串行模式

使用 test.describe.serial() 可以将依赖的测试分组,确保它们总是一起且按顺序运行。如果其中一个测试失败,后续的所有测试都会被跳过。分组内的所有测试会一起被重试。

请参考以下使用 test.describe.serial 的代码片段:

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

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

test.beforeAll(async () => { /* ... */ });
test('第一个正常', async ({ page }) => { /* ... */ });
test('第二个不稳定', async ({ page }) => { /* ... */ });
test('第三个正常', async ({ page }) => { /* ... */ });

当不启用重试机制时,失败后的所有测试都会被跳过:

  • Worker 进程 #1:
    • 执行 beforeAll 钩子
    • 第一个正常 通过
    • 第二个不稳定 失败
    • 第三个正常 被完全跳过

当启用重试机制时,所有测试会一起被重试:

  • Worker 进程 #1:
    • 执行 beforeAll 钩子
    • 第一个正常 通过
    • 第二个不稳定 失败
    • 第三个正常 被跳过
  • Worker 进程 #2:
    • 再次执行 beforeAll 钩子
    • 第一个正常 再次通过
    • 第二个不稳定 通过
    • 第三个正常 通过
备注

通常建议让你的测试相互隔离,这样可以高效地独立运行和重试。

在多个测试间复用单个页面

Playwright Test 会为每个测试创建一个独立的 Page 对象。不过,如果你希望在多个测试之间复用同一个 Page 对象,可以在 test.beforeAll() 中自行创建,并在 test.afterAll() 中关闭它。

example.spec.ts
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();
});