リトライ
はじめに
テストのリトライは、テストが失敗したときに自動的に再実行する方法です。これは、テストが不安定で断続的に失敗する場合に役立ちます。テストのリトライは、設定ファイルで設定します。
失敗
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
設定ファイルでリトライを設定できます
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()で閉じることができます。
- TypeScript
- JavaScript
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();
});
// @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.dokyumento.jp/');
});
test('runs second', async () => {
await page.getByText('Get Started').click();
});