メインコンテンツへスキップ

再試行

はじめに

テストの再試行は、テストが失敗したときに自動的に再実行する方法です。これは、テストが不安定で断続的に失敗する場合に役立ちます。テストの再試行は、設定ファイルで構成されます。

失敗

Playwright Testは、ワーカープロセスでテストを実行します。これらのプロセスは、テストランナーによってオーケストレートされ、独立して実行されるOSプロセスです。すべてのワーカーは同一の環境を持ち、それぞれが独自のブラウザを起動します。

以下のスニペットを考慮してください

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フックが実行されます

再試行を有効にすると、2番目のワーカープロセスは失敗したテストを再試行することから開始し、そこから続行します。

  • ワーカープロセス #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` を使用して実行時に再試行を検出できます。これは、任意のテスト、フック、またはフィクスチャからアクセスできます。以下は、再試行前にサーバー側の状態をクリアする例です。

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 がパスします

通常、テストを分離して、効率的に独立して実行および再試行できるようにすることが推奨されます。

テスト間での単一ページの再利用

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('runs first', async () => {
await page.goto('https://playwright.dokyumento.jp/');
});

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