QA How-To
How to Use Playwright test.describe.configure (2026)
Learn Playwright test.describe.configure for parallel, default, and serial modes, group retries, scoped timeouts, hooks, CI behavior, and safe test design.
23 min read | 2,877 words
TL;DR
Call `test.describe.configure({ mode: 'parallel' | 'default' | 'serial', retries, timeout })` at file scope or inside a `test.describe` callback. Prefer isolated tests and default or parallel execution, and use serial only when a workflow cannot be split into independently retryable tests.
Key Takeaways
- Use test.describe.configure at file scope or inside a describe to set mode, retries, and per-test timeout.
- Choose default mode for isolated tests that should run in declaration order within a file.
- Choose parallel mode only when tests, hooks, accounts, and server data are independent.
- Reserve serial mode for genuinely dependent flows because later tests skip and the group retries together.
- Use a nested default scope to opt out of an inherited fully parallel policy.
- Remember that configured timeout applies to each test, not to the combined describe block.
- Validate execution design with worker-aware diagnostics and failure-path tests.
Playwright test.describe.configure sets execution mode, retry count, and timeout for the enclosing file or describe scope. Use it when one group needs different scheduling or failure policy from the project defaults, for example independent tests that can run in parallel or a narrow group with a longer per-test timeout.
The method is small, but its consequences reach worker processes, hooks, data isolation, retries, and reporting. This guide explains the three modes, precedence through nested scopes, safe examples, and the design questions a senior SDET should answer before changing execution behavior.
TL;DR
import { test } from '@playwright/test';
test.describe.configure({
mode: 'parallel',
retries: 1,
timeout: 45_000,
});
| Option | Accepted value | Meaning |
|---|---|---|
mode |
'default' |
Tests in a file or group run in order, retries remain independent |
mode |
'parallel' |
Eligible tests in the scope may run in separate workers |
mode |
'serial' |
Tests are dependent, later tests skip after failure, group retries together |
retries |
Nonnegative number | Retry attempts for tests in the scope |
timeout |
Milliseconds | Timeout for each test in the scope |
The method can be called at top level or inside a describe callback. Configuration applies to the entire enclosing scope even if the call appears after a test declaration, but placing it before tests is easier for humans to review.
1. What Playwright test.describe.configure Does
test.describe.configure changes runner policy for the scope in which it is called. At the top level of a spec file, the scope is that file. Inside test.describe, the scope is that group. It accepts three optional properties: mode, retries, and timeout. It does not set browser context options, create fixtures, order files, or change assertion timeouts.
// tests/search.spec.ts
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'default', retries: 1, timeout: 30_000 });
test('finds documentation', async ({ page }) => {
await page.goto('https://playwright.dev');
await page.getByRole('button', { name: 'Search' }).click();
await page.getByPlaceholder('Search docs').fill('locators');
await expect(page.getByRole('dialog')).toContainText(/locators/i);
});
The snippet uses supported syntax, although public-site UI selectors can evolve. In your suite, use routes and locators owned by the product. retries: 1 means one retry attempt after the original failure. timeout: 30_000 is the maximum time for this test, including its test-scoped setup, body, and teardown behavior governed by Playwright timeouts. It is not a 30-second budget shared by all tests in the file.
Configuration affects the enclosing scope regardless of whether the call appears before or after declarations. Still, put it near the opening of the scope. Source order should communicate policy even when the runner does not require that visual order.
2. Understand Default, Parallel, and Serial Modes
The modes encode three different dependency assumptions. Choosing one is an architectural decision, not a speed switch.
| Mode | Tests inside a file | Failure and retry behavior | Best fit | Main risk |
|---|---|---|---|---|
default |
Run in declaration order | Each failed test retries independently | Normal isolated cases | Accidental belief that order creates dependency support |
parallel |
May run concurrently in separate workers | Each failed test retries independently | Independent tests with isolated data | Collisions in accounts, records, ports, or shared variables |
serial |
Run in order as a dependent group | Later tests skip after failure, whole group retries | Unavoidable staged workflow | Poor diagnosis and repeated earlier steps |
Playwright runs test files in parallel by default, subject to worker configuration. Tests inside one file normally run in order. mode: 'parallel' changes that inner-file behavior. Each parallel test executes with worker isolation and cannot rely on in-memory variables from another worker. Relevant hooks, including beforeAll and afterAll, run for each parallel test because each behaves like its own worker group.
mode: 'default' restores normal ordered execution inside the scope. It is especially useful when the project enables fullyParallel but a particular group should remain independently retryable and run in order. Ordered does not mean dependent. Each test must still set up the state it needs and remain valid when selected alone.
mode: 'serial' explicitly tells the runner that cases depend on one another. A failure skips subsequent cases. With retries, the group restarts from its beginning. Playwright documentation discourages serial design in most situations because isolated tests are easier to parallelize, retry, and diagnose.
3. Configure Parallel Mode for Independent Tests
Parallel mode is appropriate when tests in one file have no shared mutable state. Each test should use separate browser contexts supplied by Playwright, separate accounts or records, and assertions that do not depend on another test finishing first.
// tests/public-pages.spec.ts
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test('home page exposes primary navigation', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page.getByRole('navigation').first()).toBeVisible();
});
test('docs page exposes the installation guide', async ({ page }) => {
await page.goto('https://playwright.dev/docs/intro');
await expect(page.getByRole('heading', { name: /installation/i })).toBeVisible();
});
test('API page exposes APIRequestContext', async ({ page }) => {
await page.goto('https://playwright.dev/docs/api/class-apirequestcontext');
await expect(page.getByRole('heading', { name: 'APIRequestContext' })).toBeVisible();
});
Do not create a module-level let page or store a record ID from the first test for the second. Parallel workers are independent processes. Even when two tests happen to reuse a worker in one run, relying on that behavior is unsupported test design.
Hooks need the same isolation. Under parallel mode, beforeAll is not a single global setup for every test in the file. It can run once per worker group, effectively once for each test in the parallel scope. Expensive shared preparation should use an idempotent API, a setup project, or a worker-scoped fixture designed for multiple workers. Server data should be keyed by testInfo.parallelIndex or a generated unique identifier when collisions are possible.
Parallel execution exposes hidden dependencies. Treat new failures as information about data architecture before adding retries. Typing Playwright fixtures explains how to supply isolated accounts and helpers through the fixture graph.
4. Use Default Mode and Override fullyParallel
Default mode preserves declaration order inside the group while keeping tests logically independent. If a project or top-level config has fullyParallel: true, a nested default scope can opt out for a set of tests.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
});
// tests/rate-limited-search.spec.ts
import { test, expect } from '@playwright/test';
test.describe('rate-limited search checks', () => {
test.describe.configure({ mode: 'default' });
test('returns relevant documentation', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveTitle(/Playwright/);
});
test('loads the API reference', async ({ page }) => {
await page.goto('https://playwright.dev/docs/api/class-test');
await expect(page.getByRole('heading', { name: 'Playwright Test' })).toBeVisible();
});
});
A default group can run in parallel with other files or sibling groups while its own tests execute in order. It does not globally set workers: 1. If an external service has a process-wide or account-wide rate limit, several files can still hit it concurrently. Solve global capacity through worker limits, isolated accounts, a concurrency-aware service fixture, or a dedicated project.
Do not use default order as an implicit sequence. Running only the second test with -g must still work. If the first creates data required by the second, use a fixture to create that data for the second or combine the scenario into one cohesive test. Ordered independent tests provide predictable local reading and controlled concurrency, not a communication channel.
5. Use Serial Mode Only for Genuine Dependency
Serial mode represents a chain in which a later test is meaningless after an earlier failure. A staged workflow might create a draft, approve it, and verify a final audit entry. The most robust implementation is normally one test with named test.step blocks because it produces one outcome for one journey. If organizational reporting requires separate cases and state cannot be recreated, serial mode is available.
// tests/serial-workflow.spec.ts
import { test, expect, type Page } from '@playwright/test';
test.describe.configure({ mode: 'serial', retries: 1 });
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.afterAll(async () => {
await page.close();
});
test('opens the documentation home', async () => {
await page.goto('https://playwright.dev');
await expect(page).toHaveTitle(/Playwright/);
});
test('continues to the introduction', async () => {
await page.goto('https://playwright.dev/docs/intro');
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
If the first test fails, the second is skipped. With one retry, Playwright restarts the serial group in a new worker and executes from the first test again. The module-level page is recreated by beforeAll. A result may therefore show earlier successful tests executing again before the originally failing stage.
This example demonstrates the supported model, not a default recommendation. Shared-page chains make individual filtering, parallelism, and failure ownership harder. Prefer one journey test when the stages form one user outcome. Prefer fixtures or API setup when each stage deserves an independent test. Use serial only after documenting why isolation would distort or excessively duplicate the scenario.
6. Configure Retries for a Describe Scope
retries lets a group differ from the project default. This can be useful for a narrow external integration whose environment policy justifies one retry, or to set zero retries for a deterministic release gate in a project that retries broadly.
// tests/external-status.spec.ts
import { test, expect } from '@playwright/test';
test.describe('external status integration', () => {
test.describe.configure({ retries: process.env.CI ? 1 : 0 });
test('returns a successful status document', async ({ request }) => {
const response = await request.get('https://playwright.dev');
expect(response.ok()).toBeTruthy();
});
});
In default and parallel modes, retries apply to each failing test independently. In serial mode, a failing test causes the entire serial group to retry from its beginning. That difference matters for runtime, side effects, and report interpretation. A payment or notification step that already succeeded may run again during a serial retry unless the target operation is idempotent.
Retries are resilience policy, not a repair for nondeterminism. Retain traces on first retry, inspect whether failures cluster by data or environment, and treat flaky results as actionable. A test that passes on retry is not equivalent to a clean first attempt. Fixing Playwright timeout failures covers diagnosis before increasing retry counts.
Place the reason for an exceptional retry policy near the group, perhaps in a comment or linked issue. Remove the override when the external constraint changes. Scattered unexplained retry settings make suite health impossible to compare.
7. Configure a Per-Test Timeout for a Group
The timeout option applies to each test in the enclosing scope. It overrides the test timeout from project or top-level config for those tests. It does not change the expect assertion timeout and is not a combined budget for the describe block.
// tests/report-export.spec.ts
import { test, expect } from '@playwright/test';
test.describe('report export', () => {
test.describe.configure({ timeout: 60_000 });
test('generates a CSV report', async ({ page }) => {
await page.goto('https://example.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toMatch(/csv$/i);
});
test('generates a PDF report', async ({ page }) => {
await page.goto('https://example.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export PDF' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toMatch(/pdf$/i);
});
});
Each test receives up to 60 seconds according to Playwright's test-timeout rules. Two tests do not share one minute. If an individual assertion needs longer than the configured expect.timeout, pass an assertion-specific timeout or adjust expect policy deliberately. Do not confuse action, navigation, assertion, fixture, and test timeouts.
A larger budget is justified only when the operation has a known service-level expectation and remains observable. First remove hard waits, use web-first assertions, start event promises before triggering actions, and inspect network or backend latency. A timeout override should describe expected product behavior, not hide a slow selector or broken readiness signal.
8. Combine Nested Scopes Without Losing Intent
A file can run sibling groups concurrently while preserving ordered tests inside each group. Configure the file as parallel, then configure each nested group as default. Playwright schedules groups against each other, while direct tests within each group run in order and retry independently.
// tests/areas.spec.ts
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test.describe('documentation area', () => {
test.describe.configure({ mode: 'default', timeout: 30_000 });
test('opens introduction', async ({ page }) => {
await page.goto('https://playwright.dev/docs/intro');
await expect(page).toHaveTitle(/Installation/);
});
test('opens writing tests', async ({ page }) => {
await page.goto('https://playwright.dev/docs/writing-tests');
await expect(page.getByRole('main')).toContainText('Writing tests');
});
});
test.describe('API area', () => {
test.describe.configure({ mode: 'default', timeout: 30_000 });
test('opens test API', async ({ page }) => {
await page.goto('https://playwright.dev/docs/api/class-test');
await expect(page.getByRole('heading', { name: 'Playwright Test' })).toBeVisible();
});
test('opens locator API', async ({ page }) => {
await page.goto('https://playwright.dev/docs/api/class-locator');
await expect(page.getByRole('heading', { name: 'Locator' })).toBeVisible();
});
});
This hierarchy is useful for medium-grained concurrency. Keep configuration at the shallowest scope that expresses a true rule. Repeating nested overrides in every group can make scheduling harder to understand than using separate files and project settings.
Configuration applies to the enclosing scope, so call it inside the correct callback. A top-level call adjacent to a describe does not target only that describe. Use indentation and placement that make ownership obvious, and avoid several configure calls in one scope when one object would communicate the final policy more clearly.
9. Understand Hooks, Workers, and Shared State
Execution mode changes how often hooks and module state are encountered. This is the most common source of surprises after enabling parallel mode.
| Concern | Default | Parallel | Serial |
|---|---|---|---|
| Tests in scope | Ordered | Concurrent when workers permit | Ordered and dependent |
beforeEach |
Once per test | Once per test | Once per test |
beforeAll |
Once per worker group | Runs for each parallel worker group | Once per serial attempt |
| Module variables | Must not carry required test state | Separate processes cannot share them | Can be shared, but creates dependency |
| Failure impact | Failed test only | Failed test only | Later tests skip |
| Retry unit | Test | Test | Entire group |
Even default mode cannot guarantee a module variable survives a failure. Playwright shuts down a worker after a test failure to provide a clean environment for following work. Required state belongs in a fixture, durable target system, or test-local setup.
Worker-scoped fixtures are reused by tests in the same worker, but parallel tests may use different workers. Create worker-unique accounts with worker information when necessary. Test-scoped fixtures are safer defaults for mutable state because setup and teardown surround one test. Automatic fixtures can capture logs consistently across modes.
Avoid beforeAll for creating mutable data that every parallel test edits. The hook can execute multiple times and produce collisions unless its operation is unique or idempotent. If preparation must happen once for a whole run, consider a setup project with explicit dependencies rather than assuming file hooks are global.
10. Integrate Scope Configuration With Projects and CI
Runner settings form layers. Top-level config establishes defaults, projects adapt execution targets, and test.describe.configure applies a local exception. Keep most policy high in the hierarchy and use local overrides only for a clearly bounded reason.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
timeout: 30_000,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 4 : undefined,
use: { trace: 'on-first-retry' },
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
A file-level mode: 'default' can opt out of fullyParallel for its scope. A file-level timeout can override the project test timeout. A local retry setting can change attempts. It cannot increase the number of worker processes beyond CI capacity, and parallel mode does not guarantee simultaneous start times. It only makes tests eligible for parallel scheduling.
CI verification should include at least one run with multiple workers and the same project matrix used for release. Local sequential success cannot prove parallel data isolation. Retain traces for retries and make project names visible in reports. If the suite is sharded, remember that sharding distributes test groups after collection, while execution modes still govern behavior inside those groups. See Playwright projects config fundamentals for hierarchy design.
11. Review Playwright test.describe.configure Before Merging
Review the change as test architecture. Ask why this scope differs from project defaults, whether its tests pass alone, and what failure behavior stakeholders will see.
Use this checklist:
- Confirm the call is at file scope or inside the intended describe callback.
- Prefer one configure call per scope with only necessary properties.
- For parallel mode, verify unique accounts, records, files, ports, and external resources.
- Run with at least two workers and repeat enough to expose data collisions.
- For default mode, run each test alone to prove order is not a dependency.
- For serial mode, force an early failure and inspect skips and full-group retries.
- For retries, retain first-retry evidence and document the exception.
- For timeout, identify the expected slow operation and confirm assertion timeouts separately.
- Inspect hook behavior and clean up resources after every attempt.
- Compare local, CI, project, and shard settings.
A good local override makes a real constraint visible. A poor one masks shared state, nondeterminism, or slow operations. If many files need the same override, promote that policy to a project or top-level config so it remains discoverable.
Interview Questions and Answers
Q: What can test.describe.configure configure?
It configures mode, retries, and per-test timeout for the enclosing file or describe scope. The modes are default, parallel, and serial. It does not configure browser context options or assertion timeout.
Q: How is parallel mode different from workers?
Parallel mode makes tests inside the scope eligible to run concurrently. workers limits the number of worker processes available to execute eligible work. Parallel mode with one worker remains effectively sequential, while more workers do not make tests inside a default file concurrent.
Q: What happens to beforeAll in parallel mode?
Parallel tests run in separate worker groups, so relevant beforeAll and afterAll hooks execute for each parallel test group. I never assume one file-level hook is global setup. Shared preparation must be idempotent, worker-safe, or moved to an explicit setup architecture.
Q: What is the retry difference between default and serial modes?
In default mode, a failed test retries independently. In serial mode, later tests skip after a failure and the whole group retries from its beginning. This makes serial retries more expensive and potentially risky for non-idempotent side effects.
Q: Why would you explicitly use default mode?
I use it to opt a scope out of fullyParallel while preserving independent retries. The tests run in order inside that scope, but each must still work alone. It is not a license to share required state.
Q: Does configure timeout apply to the whole describe?
No. It sets the timeout for each test in the scope. It also does not replace assertion-specific timeout policy. I diagnose the slow layer before increasing it.
Q: When is serial mode acceptable?
Only when the cases truly form a dependent sequence and converting them to one journey or independent fixture-backed tests would be misleading or impractical. I document the reason, test failure behavior, and make all side effects safe to repeat.
Q: Where can the configure call appear?
It can run at top level for the file or inside a describe for that group. Its configuration applies to the entire enclosing scope regardless of declaration order. I still place it before tests because source readability matters.
Common Mistakes
- Treating
mode: 'parallel'as a harmless speed flag without isolating data. - Assuming a file-level
beforeAllruns only once when tests are parallel. - Using module variables to pass required state between default-mode tests.
- Choosing serial mode to hide dependencies that fixtures could remove.
- Forgetting that a serial retry restarts the entire group.
- Believing
timeoutis one budget for all tests in the describe. - Increasing timeout when the actual issue is an assertion, locator, or readiness signal.
- Expecting parallel mode to override a one-worker CI limit.
- Placing a top-level configure call next to a describe and assuming it affects only that group.
- Scattering the same override across many files instead of defining a project policy.
- Testing only locally with one worker after changing execution mode.
Conclusion
Playwright test.describe.configure is the right tool for a bounded execution-policy exception. Use default for ordered but independent tests, parallel for truly isolated cases, and serial only for unavoidable dependency. Configure retries and timeouts with an explicit operational reason.
Start by running every test alone, then test the scope under the intended worker count and failure path. If the exception repeats across the suite, move it into project configuration so scheduling remains visible and maintainable.
Interview Questions and Answers
Explain test.describe.configure and its scope.
It changes mode, retries, and per-test timeout for the enclosing scope. At top level that scope is the file, and inside a describe it is that group. The policy applies to all tests in the scope regardless of where the call appears relative to declarations.
Compare Playwright default, parallel, and serial modes.
Default runs tests in order with independent retries. Parallel makes tests eligible for separate workers and requires complete state isolation. Serial declares dependency, skips later tests after failure, and retries the whole group from the start.
How do fullyParallel and mode default interact?
A project can enable fully parallel execution broadly. A describe scope can explicitly configure default mode to opt out locally, keeping its tests ordered and independently retryable. Other files and groups may still run concurrently.
How would you make a parallel group data-safe?
Each test gets isolated browser state and uniquely provisioned server data. Worker or parallel indices can help allocate accounts, while cleanup is idempotent. I also run the group repeatedly with the real CI worker count and inspect service-side collisions.
Why are serial retries risky for side effects?
The whole group restarts, so earlier successful operations execute again. Payments, messages, or record transitions may duplicate unless they are idempotent or cleaned up. This is one reason I prefer an independent setup or a single cohesive journey.
How do scoped retries affect flakiness management?
They can express a narrow policy, but a retry must retain evidence and remain visible as flaky. I use traces on retry, diagnose the failure class, and remove exceptional retries when the root cause is fixed. Retries are not a substitute for isolation.
How do hooks behave in a parallel describe?
Per-test hooks run for each test as usual. Each parallel test can run in its own worker group, so beforeAll and afterAll are repeated for those groups. Hook-created resources must therefore be safe to create concurrently and clean up independently.
What review evidence would you request for an execution-mode change?
I want targeted runs with the intended worker count, each test run alone, and a forced-failure demonstration. For parallel changes I check collision-free data. For serial changes I inspect skipped cases, group retries, repeated side effects, and the documented reason dependency remains necessary.
Frequently Asked Questions
What options does Playwright test.describe.configure support?
It supports `mode`, `retries`, and `timeout`. Mode accepts `default`, `parallel`, or `serial`, while retries is a number and timeout is the per-test limit in milliseconds.
Can test.describe.configure be used at file level?
Yes. Call it at the top level of a spec file to configure that file's scope. Call it inside a `test.describe` callback to configure only that group.
Does Playwright parallel mode share variables between tests?
No. Parallel tests can run in separate worker processes and cannot rely on shared in-memory state. Use isolated fixtures, accounts, records, and files.
What does default mode mean in Playwright?
Tests in the file or group run in declaration order and retry independently. Default mode can opt a scope out of project-wide fully parallel execution, but tests should still be independent.
What happens when a serial Playwright test fails?
Following tests in the serial group are skipped. If retries are enabled, Playwright retries the group from its beginning in a fresh worker.
Does test.describe.configure timeout apply to every test?
Yes. Each test in the enclosing scope receives that test timeout. It is not divided across the group and does not automatically change the configured assertion timeout.
Can I override fullyParallel for one describe block?
Yes. Put `test.describe.configure({ mode: 'default' })` inside that describe callback. Its tests run in order within the group while other eligible work can remain parallel.
Should I use serial mode to reuse one page?
Playwright supports that pattern, but it should be exceptional. One cohesive test with steps or independent tests with fixture-based setup usually provides better isolation, filtering, retries, and diagnosis.