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

リトライ

はじめに

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

失敗

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はテストを次のように分類します。

  • "合格" - 初回実行で合格したテスト。
  • "不安定" - 初回実行で失敗したが、リトライ時に合格したテスト。
  • "失敗" - 初回実行で失敗し、すべてのリトライでも失敗したテスト。
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();
});