重试

介绍

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

失败

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

考虑以下代码示例:

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 () => { /* ... */ });
});

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

  • 工作进程启动

    • beforeAll 钩子运行

    • first good 通过

    • second flaky 通过

    • third good 通过

    • afterAll 钩子运行

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

  • 工作进程 #1 启动

    • beforeAll 钩子运行

    • first good 通过

    • second flaky 失败

    • afterAll 钩子运行

  • 工作进程 #2 启动

    • beforeAll 钩子再次运行

    • third good 通过

    • afterAll 钩子运行

如果 启用重试,第二个工作进程将通过重试失败的测试并继续执行:

  • 工作进程 #1 启动

    • beforeAll 钩子运行

    • first good 通过

    • second flaky 失败

    • afterAll 钩子运行

  • 工作进程 #2 启动

    • beforeAll 钩子再次运行

    • second flaky 被重试并通过

    • third good 通过

    • afterAll 钩子运行

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

重试

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

命令行启用重试:

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

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

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

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

Playwright Test 将测试分类如下:

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

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

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

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 来在运行时检测重试次数,它可以在任何测试、钩子或 fixture 中访问。以下是一个在重试前清除服务器端状态的例子:

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

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

你可以为特定的测试组或单个文件指定重试次数,使用 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 }) => {
    // ...
  });
});

串行模式

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

考虑以下代码示例,使用 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 }) => { /* ... */ });

如果不使用重试,所有失败后的测试将被跳过:

  • 工作进程 #1:

    • beforeAll 钩子运行

    • first good 通过

    • second flaky 失败

    • third good 被完全跳过

如果使用重试,所有测试将一起重试:

  • 工作进程 #1:

    • beforeAll 钩子运行

    • first good 通过

    • second flaky 失败

    • third good 被跳过

  • 工作进程 #2:

    • beforeAll 钩子再次运行

    • first good 再次通过

    • second flaky 通过

    • third good 通过

通常,最好让测试相互独立,这样可以更高效地运行和重试。

在多个测试之间重用同一个 Page 对象

Playwright Test 为每个测试创建一个隔离的 【Page】 对象。然而,如果你希望在多个测试之间重用一个 【Page】 对象,可以在 test.beforeAll() 中创建它,并在 test.afterAll() 中关闭它。

  • TypeScript

  • JavaScript

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.dev/');
});

test('runs second', async () => {
  await page.getByText('Get Started').click();
});
example.spec.js
// @ts-check

const { test } = require('@playwright/test');

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

/** @type {import('@playwright/test').Page} */
let page;

test.beforeAll(async ({ browser }) => {
  page = await browser.newPage();
});

test.afterAll(async () => {
  await page.close();
});

test('runs first', async () => {
  await page.goto('https://playwright.dev/');
});

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