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

¥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.

  • 工作进程启动

    ¥Worker process starts

    • beforeAll 钩子运行

      ¥beforeAll hook runs

    • first good 次通过

      ¥first good passes

    • second flaky 次通过

      ¥second flaky passes

    • third good 次通过

      ¥third good passes

    • afterAll 钩子运行

      ¥afterAll hook runs

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

¥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 启动

    ¥Worker process #1 starts

    • beforeAll 钩子运行

      ¥beforeAll hook runs

    • first good 次通过

      ¥first good passes

    • second flaky 失败

      ¥second flaky fails

    • afterAll 钩子运行

      ¥afterAll hook runs

  • 工作进程 #2 启动

    ¥Worker process #2 starts

    • beforeAll 钩子再次运行

      ¥beforeAll hook runs again

    • third good 次通过

      ¥third good passes

    • afterAll 钩子运行

      ¥afterAll hook runs

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

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

  • 工作进程 #1 启动

    ¥Worker process #1 starts

    • beforeAll 钩子运行

      ¥beforeAll hook runs

    • first good 次通过

      ¥first good passes

    • second flaky 失败

      ¥second flaky fails

    • afterAll 钩子运行

      ¥afterAll hook runs

  • 工作进程 #2 启动

    ¥Worker process #2 starts

    • beforeAll 钩子再次运行

      ¥beforeAll hook runs again

    • 重试 second flaky 并通过

      ¥second flaky is retried and passes

    • third good 次通过

      ¥third good passes

    • afterAll 钩子运行

      ¥afterAll hook runs

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

¥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:

  • "passed" - 第一次运行时通过的测试;

    ¥"passed" - tests that passed on the first run;

  • "flaky" - 第一次运行失败但重试时通过的测试;

    ¥"flaky" - tests that failed on the first run, but passed when retried;

  • "failed" - 第一次运行失败且所有重试均失败的测试。

    ¥"failed" - tests that failed on the first run and failed all retries.

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:

    ¥Worker process #1:

    • beforeAll 钩子运行

      ¥beforeAll hook runs

    • first good 次通过

      ¥first good passes

    • second flaky 失败

      ¥second flaky fails

    • third good 被完全跳过

      ¥third good is skipped entirely

当使用 retries 运行时,所有测试都会一起重试:

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

  • 工作进程#1:

    ¥Worker process #1:

    • beforeAll 钩子运行

      ¥beforeAll hook runs

    • first good 次通过

      ¥first good passes

    • second flaky 失败

      ¥second flaky fails

    • third good 被跳过

      ¥third good is skipped

  • 工作进程#2:

    ¥Worker process #2:

    • beforeAll 钩子再次运行

      ¥beforeAll hook runs again

    • first good 再次通过

      ¥first good passes again

    • second flaky 次通过

      ¥second flaky passes

    • third good 次通过

      ¥third good passes

注意

通常最好将测试隔离起来,这样它们就可以有效地运行并独立重试。

¥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();
});