Skip to main content

重试

介绍

🌐 Introduction

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

🌐 Test retries are a way to automatically re-run a test when it fails. This is useful when a test is flaky and fails intermittently. Test retries are configured in the configuration file.

失败

🌐 Failures

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

🌐 Playwright Test runs tests 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.

考虑以下片段:

🌐 Consider the following snippet:

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

test.describe('suite', () => {
test.beforeAll(async () => { /* ... */ });
test('first good', async ({ page }) => { /* ... */ });
test('second flaky', async ({ page }) => { /* ... */ });
test('third good', async ({ page }) => { /* ... */ });
test.afterAll(async () => { /* ... */ });
});

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

🌐 When all tests pass, they will run in order in the same worker process.

  • 工作进程启动
    • beforeAll 钩子运行
    • first good 通过
    • second flaky 通过
    • third good 通过
    • afterAll 钩子运行

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

🌐 Should any test fail, Playwright Test will discard the entire worker process along with the browser and will start a new one. Testing will continue in the new worker process starting with the next test.

  • 工作进程 #1 启动
    • beforeAll 钩子运行
    • first good 通过
    • second flaky 失败
    • afterAll 钩子运行
  • 工作进程 #2 启动
    • beforeAll 钩子再次运行
    • third good 通过
    • afterAll 钩子运行

如果你启用 重试,第二个工作进程将通过重试失败的测试开始,并从那里继续。

🌐 If you enable retries, second worker process will start by retrying the failed test and continue from there.

  • 工作进程 #1 启动
    • beforeAll 钩子运行
    • first good 通过
    • second flaky 失败
    • afterAll 钩子运行
  • 工作进程 #2 启动
    • beforeAll 钩子再次运行
    • second flaky 已重试并通过
    • third good 通过
    • afterAll 钩子运行

该方案非常适合独立测试,并保证失败的测试不会影响健康的测试。

🌐 This scheme works perfectly for independent tests and guarantees that failing tests can't affect healthy ones.

重试

🌐 Retries

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

🌐 Playwright supports test retries. When enabled, failing tests will be retried multiple times until they pass, or until the maximum number of retries is reached. By default failing tests are not retried.

# Give failing tests 3 retry attempts
npx playwright test --retries=3

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

🌐 You can configure retries in the configuration file:

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

export default defineConfig({
// Give failing tests 3 retry attempts
retries: 3,
});

Playwright 测试将测试分类如下:

🌐 Playwright Test will categorize tests as follows:

  • “通过”——第一次运行就通过的测试;
  • “不稳定”——在第一次运行时失败,但重试后通过的测试;
  • “失败” - 第一次运行就失败且所有重试均失败的测试。
Running 3 tests using 1 worker

✓ example.spec.ts:4:2 › first passes (438ms)
x example.spec.ts:5:2 › second flaky (691ms)
✓ example.spec.ts:5:2 › second flaky (522ms)
✓ example.spec.ts:6:2 › third passes (932ms)

1 flaky
example.spec.ts:5:2 › second flaky
2 passed (4s)

你可以在运行时通过 testInfo.retry 检测重试,这对任何测试、钩子或夹具都是可访问的。以下是一个在重试之前清除部分服务器端状态的示例。

🌐 You can detect retries at runtime with testInfo.retry, which is accessible to any test, hook or fixture. Here is an example that clears some server-side state before a retry.

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

test('my test', async ({ page }, testInfo) => {
if (testInfo.retry)
await cleanSomeCachesOnTheServer();
// ...
});

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

🌐 You can specify retries for a specific group of tests or a single file with test.describe.configure().

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

test.describe(() => {
// All tests in this describe group will get 2 retry attempts.
test.describe.configure({ retries: 2 });

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

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

串行模式

🌐 Serial mode

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

🌐 Use test.describe.serial() to group dependent tests to ensure they will always run together and in order. If one of the tests fails, all subsequent tests are skipped. All tests in the group are retried together.

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

🌐 Consider the following snippet that uses test.describe.serial:

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

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

test.beforeAll(async () => { /* ... */ });
test('first good', async ({ page }) => { /* ... */ });
test('second flaky', async ({ page }) => { /* ... */ });
test('third good', async ({ page }) => { /* ... */ });

当运行时没有[重试](#retries),失败后的所有测试都被跳过:

🌐 When running without retries, all tests after the failure are skipped:

  • 工作进程#1:
    • beforeAll 钩子运行
    • first good 通过
    • second flaky 失败
    • third good 被完全跳过

在使用 重试 运行时,所有测试将一起重试:

🌐 When running with retries, all tests are retried together:

  • 工作进程#1:
    • beforeAll 钩子运行
    • first good 通过
    • second flaky 失败
    • third good 被跳过
  • 工作进程#2:
    • beforeAll 钩子再次运行
    • first good 又通过了
    • second flaky 通过
    • third good 通过
note

通常最好让你的测试保持独立,这样它们可以高效地独立运行和重试。

🌐 It is usually better to make your tests isolated, so they can be efficiently run and retried independently.

在测试之间重用单页

🌐 Reuse single page between tests

Playwright Test 会为每个测试创建一个独立的 Page 对象。然而,如果你想在多个测试之间重用同一个 Page 对象,可以在 test.beforeAll() 中创建它,并在 test.afterAll() 中关闭它。

🌐 Playwright Test creates an isolated Page object for each test. However, if you'd like to reuse a single Page object between multiple tests, you can create your own in test.beforeAll() and close it in 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('runs first', async () => {
await page.goto('https://playwright.nodejs.cn/');
});

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