Test isolation definition and purpose
Test isolation means each test is completely isolated from another test and runs independently. Each test has its own local storage, session storage, and cookies. Playwright achieves this using BrowserContexts, which are equivalent to incognito-like profiles. Playwright creates a context for each test and provides a default Page in that context.
Benefits of test isolation
Test isolation prevents failure carry-over so if one test fails it does not affect other tests. It makes debugging easier because you can run a single test as many times as needed. It eliminates the need to think about test order when running in parallel or with sharding.
Two test isolation strategies
There are two strategies for test isolation: start from scratch or cleanup in between tests. Starting from scratch is preferable because everything is new, so if a test fails you only have to debug within that test. Cleaning up in between tests can lead to forgotten cleanups and some things are impossible to clean up, such as visited links, which can cause state leakage into the next test.
How Playwright achieves test isolation
Playwright uses browser contexts to achieve test isolation. Each test has its own BrowserContext. When using Playwright as a Test Runner, browser contexts are created by default. Otherwise, you can create browser contexts manually.
Test tags must start with @ symbol
Each test tag must start with the @ symbol. Tags can be provided via the tag property in test details or included directly in the test title. Tags are displayed in test reports and available via TestCase.tags property.
test.describe() declares a test group
test.describe() declares a group of tests. Signature: test.describe(title, callback), test.describe(callback) for anonymous group, or test.describe(title, details, callback). The title appears in test reports as part of each test's title. Anonymous groups are useful for applying common options via test.use().
test.describe.configure() sets execution mode
test.describe.configure() configures the enclosing scope with options: mode ('default', 'parallel', or 'serial'), retries (number), and timeout (milliseconds). Configuration applies to the entire scope regardless of declaration order. Can be used at file level or inside describe blocks.
test.describe.skip() skips a test group
test.describe.skip() declares a skipped test group similar to test.describe(). Tests in the skipped group are never run. Signature: test.describe.skip(title, callback), test.describe.skip(callback) for anonymous, or test.describe.skip(title, details, callback).
test.describe.fixme() declares unfixed test group
test.describe.fixme() declares a test group marked as 'fixme'. Tests in this group are not executed. Signature: test.describe.fixme(title, callback), test.describe.fixme(callback) for anonymous, or test.describe.fixme(title, details, callback).
test.describe.only() focuses a test group
test.describe.only() declares a focused test group. If there are focused tests or suites, only they run. Signature: test.describe.only(title, callback), test.describe.only(callback) for anonymous, or test.describe.only(title, details, callback).
test.describe.parallel() runs group tests in parallel
test.describe.parallel() declares a group of tests that run in parallel. By default, tests in a file run sequentially, but this allows them to run concurrently. Parallel tests execute in separate processes and cannot share state. Signature: test.describe.parallel(title, callback), test.describe.parallel(callback), or test.describe.parallel(title, details, callback). Discouraged in favor of test.describe.configure({ mode: 'parallel' }).
test.describe.serial() runs group tests serially
test.describe.serial() declares a group of tests that run serially. If one test fails, all subsequent tests are skipped. All tests retry together. Signature: test.describe.serial(title, callback), test.describe.serial(callback), or test.describe.serial(title, details, callback). Not recommended; tests should be isolated. Discouraged in favor of test.describe.configure({ mode: 'serial' }).
test.describe() group example
test.describe('two tests', () => {
test('one', async ({ page }) => {
// ...
});
test('two', async ({ page }) => {
// ...
});
});
Anonymous describe group with test.use()
test.describe(() => {
test.use({ colorScheme: 'dark' });
test('one', async ({ page }) => {
// ...
});
test('two', async ({ page }) => {
// ...
});
});
test.describe.configure() with mode parallel
test.describe.configure({ mode: 'parallel' });
test('runs in parallel 1', async ({ page }) => {});
test('runs in parallel 2', async ({ page }) => {});
test.describe.configure() with mode default
test.describe.configure({ mode: 'default' });
test('runs first', async ({ page }) => {});
test('runs second', async ({ page }) => {});
test.describe.configure() with mode serial
test.describe.configure({ mode: 'serial' });
test('runs first', async ({ page }) => {});
test('runs second', async ({ page }) => {});
test.describe.configure() with retries and timeout
test.describe.configure({ retries: 2, timeout: 20_000 });
test('runs first', async ({ page }) => {});
test('runs second', async ({ page }) => {});
Nested describe groups with different modes
test.describe.configure({ mode: 'parallel' });
test.describe('A, runs in parallel with B', () => {
test.describe.configure({ mode: 'default' });
test('in order A1', async ({ page }) => {});
test('in order A2', async ({ page }) => {});
});
test.describe('B, runs in parallel with A', () => {
test.describe.configure({ mode: 'default' });
test('in order B1', async ({ page }) => {});
test('in order B2', async ({ page }) => {});
});
test.describe.fixme() example
test.describe.fixme('broken tests that should be fixed', () => {
test('example', async ({ page }) => {
// This test will not run
});
});
test.describe.only() example
test.describe.only('focused group', () => {
test('in the focused group', async ({ page }) => {
// This test will run
});
});
test('not in the focused group', async ({ page }) => {
// This test will not run
});
test.describe.skip() example
test.describe.skip('skipped group', () => {
test('example', async ({ page }) => {
// This test will not run
});
});
Test tags can be included in test title
import { test, expect } from '@playwright/test';
test('another test @smoke', async ({ page }) => {
await page.goto('https://playwright.dev/');
// ...
});
Test describe group with tags in details
import { test, expect } from '@playwright/test';
test.describe('two tagged tests', {
tag: '@smoke',
}, () => {
test('one', async ({ page }) => {
// ...
});
test('two', async ({ page }) => {
// ...
});
});
Test describe group with annotation
import { test, expect } from '@playwright/test';
test.describe('two annotated tests', {
annotation: {
type: 'issue',
description: 'https://github.com/microsoft/playwright/issues/23180',
},
}, () => {
test('one', async ({ page }) => {
// ...
});
test('two', async ({ page }) => {
// ...
});
});
Test describe group with lock
import { test, expect } from '@playwright/test';
test.describe('two tests with a lock', {
lock: 'user-settings',
}, () => {
test('one', async ({ page }) => {
// ...
});
test('two', async ({ page }) => {
// ...
});
});
test.describe.configure() timeout option
The timeout option in test.describe.configure() sets timeout for each test in milliseconds. It overrides TestProject.timeout and TestConfig.timeout at the project and config level. Minimum version v1.28.
test.describe.parallel.only() focuses parallel group
test.describe.parallel.only() declares a focused group of tests that run in parallel. If there are focused tests or suites, only they run. Discouraged in favor of test.describe.configure({ mode: 'parallel' }).
test.describe.parallel.only() example
test.describe.parallel.only('group', () => {
test('runs in parallel 1', async ({ page }) => {});
test('runs in parallel 2', async ({ page }) => {});
});
test.describe.serial.only() focuses serial group
test.describe.serial.only() declares a focused group of tests that run serially. If one fails, all subsequent tests skip. All tests retry together. If there are focused tests or suites, only they run. Not recommended. Discouraged in favor of test.describe.configure().
test.describe.serial.only() example
test.describe.serial.only('group', () => {
test('runs first', async ({ page }) => {
});
test('runs second', async ({ page }) => {
});
});