Test retry
#
FailuresPlaywright 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:
- TypeScript
- JavaScript
import { test } from '@playwright/test';
test.describe('suite', () => { test('first good', async ({ page }) => { /* ... */ }); test('second flaky', async ({ page }) => { /* ... */ }); test('third good', async ({ page }) => { /* ... */ });});
const { test } = require('@playwright/test');
test.describe('suite', () => { test('first good', async ({ page }) => { /* ... */ }); test('second flaky', async ({ page }) => { /* ... */ }); test('third good', async ({ page }) => { /* ... */ });});
When all tests pass, they will run in order in the same worker process.
- Worker process starts
first good
passessecond flaky
passesthird good
passes
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.
- Worker process #1 starts
first good
passessecond flaky
fails
- Worker process #2 starts
third good
passes
If you enable retries, second worker process will start by retrying the failed test and continue from there.
- Worker process #1 starts
first good
passessecond flaky
fails
- Worker process #2 starts
second flaky
is retried and passesthird good
passes
This scheme works perfectly for independent tests and guarantees that failing tests can't affect healthy ones.
#
RetriesPlaywright Test supports test retries. When enabled, failing tests will be retried multiple times until they pass, or until the maximum number of retries is reached.
# Give failing tests 3 retry attemptsnpx playwright test --retries=3
- TypeScript
- JavaScript
// playwright.config.tsimport { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = { // Give failing tests 3 retry attempts retries: 3,};export default config;
// playwright.config.js// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */const config = { // Give failing tests 3 retry attempts retries: 3,};
module.exports = config;
Playwright Test will categorize tests as follows:
- "passed" - tests that passed on the first run;
- "flaky" - tests that failed on the first run, but passed when retried;
- "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)
#
Serial modeUse test.describe.serial(title, callback) 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.
Consider the following snippet that uses test.describe.serial
:
- TypeScript
- JavaScript
import { test } from '@playwright/test';
test.describe.serial('suite', () => { test('first good', async ({ page }) => { /* ... */ }); test('second flaky', async ({ page }) => { /* ... */ }); test('third good', async ({ page }) => { /* ... */ });});
const { test } = require('@playwright/test');
test.describe.serial('suite', () => { test('first good', async ({ page }) => { /* ... */ }); test('second flaky', async ({ page }) => { /* ... */ }); test('third good', async ({ page }) => { /* ... */ });});
When running without retries, all tests after the failure are skipped:
- Worker process #1:
first good
passessecond flaky
failsthird good
is skipped entirely
When running with retries, all tests are retried together:
- Worker process #1:
first good
passessecond flaky
failsthird good
is skipped
- Worker process #2:
first good
passes againsecond flaky
passesthird good
passes
note
It is usually better to make your tests isolated, so they can be efficiently run and retried independently.
#
Reuse single page between testsPlaywright 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(hookFunction) and close it in test.afterAll(hookFunction).
- TypeScript
- JavaScript
// example.spec.ts
import { test, Page } from '@playwright/test';
test.describe.serial('use the same page', () => { 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.click('text=Get Started'); });});
// example.spec.js// @ts-check
const { test } = require('@playwright/test');
test.describe.serial('use the same page', () => { /** @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.click('text=Get Started'); });});