PRACTICAL GUIDE / playwright timeout configuration
Budget Playwright Timeouts Across Tests, Fixtures, Actions, and Assertions
Budget Playwright timeouts across tests, fixtures, actions, navigation, and assertions so failures identify the stalled layer instead of merely waiting longer.
In this guide13 sections
- Map the Clocks Before Changing Them
- Start with a Suite Runtime Target
- Give the Test One Coherent Outer Budget
- Isolate Slow Fixture Provisioning
- Keep Action Timeouts Short and Diagnostic
- Separate Navigation from Post-Navigation Readiness
- Set Assertion Windows by State Class
- Account for Hooks and Teardown
- Diagnose the Expired Layer
- Avoid Timeout Anti-Patterns
- Review the Tradeoffs
- Operational Checklist
- Conclusion: Make the Expired Clock Name the Problem
What you will learn
- Map the Clocks Before Changing Them
- Start with a Suite Runtime Target
- Give the Test One Coherent Outer Budget
- Isolate Slow Fixture Provisioning
A timeout is a resource budget and a diagnostic boundary. It should tell the team whether provisioning stalled, a button never became actionable, navigation did not begin, or an expected business state failed to appear. Raising every Playwright timeout to the same large number destroys that information and makes genuine hangs expensive.
Build the policy from the outside inward. Set a cap for the complete run, a normal budget for one test, explicit exceptions for slow fixtures, shorter limits for browser operations, and an assertion window based on the state being observed. Every inner clock must fit inside the work that owns it.
Map the Clocks Before Changing Them
The official Playwright timeout documentation separates test, assertion, global, action, navigation, hook, and fixture timeouts. Read the error and call log to identify which clock expired. A test timeout message and an expect.toHaveText timeout are different failures even when they occur on the same line.
The normal test budget includes the test function, fixture setup, and beforeEach hooks. Fixture teardown and afterEach work receive a separate teardown allowance based on the test timeout after the test function finishes. beforeAll and afterAll have their own hook budgets. Assertion timeout is independent, but it cannot keep a test alive after the outer test budget is exhausted.
Animated field map
A Layered Playwright Timeout Budget
The suite target narrows into test, fixture, action, navigation, and assertion clocks that identify the layer which stopped progressing.
01 / suite runtime
Suite Runtime Target
Cap the complete CI run so systemic failure cannot consume unlimited resources.
02 / test budget
Test Budget
Bound one scenario together with its setup fixtures and beforeEach hooks.
03 / fixture budget
Fixture Budget
Isolate legitimately slow provisioning from ordinary scenario execution.
04 / operation budgets
Action and Assertion
Limit browser operations and state polling with purpose-specific deadlines.
05 / timeout diagnosis
Timeout Diagnosis
Use the expired layer, trace, and call log to locate lost progress.
Start with a Suite Runtime Target
globalTimeout protects the CI system when many workers hang, infrastructure is unavailable, or retries multiply a broad failure. Set it from the job's service-level target and leave room for report upload and teardown. It is a circuit breaker, not a performance objective for individual tests.
The following numbers illustrate a policy shape, not universal recommendations. The correct values come from your product's normal and exceptional lifecycles.
import { defineConfig } from "@playwright/test";
export default defineConfig({
globalTimeout: process.env.CI ? 45 * 60_000 : undefined,
timeout: 45_000,
expect: {
timeout: 7_000,
},
use: {
actionTimeout: 10_000,
navigationTimeout: 20_000,
trace: "on-first-retry",
},
});If most tests need the maximum, the baseline is not describing normal behavior. Measure which phase consumes time before increasing it.
Give the Test One Coherent Outer Budget
The test budget should cover normal setup and the complete scenario with enough margin for assertion retries. It should not be calculated as the sum of every declared inner timeout, because operations usually finish early. It should still be large enough that a legitimate final assertion is not cut off by setup consuming nearly all available time.
Use test.setTimeout for a named exception whose workflow is intentionally longer. Use test.slow when the whole test category deserves the framework's slow classification. Put the reason next to the override and review exceptions periodically.
import { expect, test } from "@playwright/test";
test("a quarterly export becomes downloadable", async ({ page }) => {
test.setTimeout(90_000);
await page.goto("/reports/quarterly");
await page.getByRole("button", { name: "Generate export" }).click();
await expect(page.getByRole("status"))
.toHaveText("Export ready", { timeout: 60_000 });
await expect(page.getByRole("link", { name: "Download CSV" }))
.toBeEnabled();
});The long assertion expresses the known asynchronous boundary. It is more informative than extending every click and navigation in the suite.
Isolate Slow Fixture Provisioning
By default, a test-scoped fixture consumes the test's setup time. A slow worker fixture can use an independent timeout so account leasing or environment provisioning does not force every test to inherit a large outer budget. The fixture must still expose progress through logs or attachments; a separate timeout should not become a silent waiting room.
import { test as base } from "@playwright/test";
type WorkerFixtures = {
seededTenant: { tenantId: string; cleanupToken: string };
};
export const test = base.extend<{}, WorkerFixtures>({
seededTenant: [async ({ request }, use) => {
const response = await request.post("/test-support/tenants", {
data: { plan: "enterprise", sampleOrders: 25 },
});
if (!response.ok()) {
throw new Error(`Tenant provisioning failed with ${response.status()}`);
}
const tenant = await response.json();
await use(tenant);
await request.delete(`/test-support/tenants/${tenant.tenantId}`, {
headers: { "x-cleanup-token": tenant.cleanupToken },
});
}, { scope: "worker", timeout: 75_000 }],
});Provisioning data should be bounded and deterministic. If setup time varies wildly, investigate service capacity, account contention, or an unobservable job rather than repeatedly increasing the fixture limit.
Keep Action Timeouts Short and Diagnostic
Actions already wait for their required actionability checks. An action timeout answers how long a click, fill, or similar browser operation may wait for one target to become usable. A long action timeout can conceal an overlay, wrong locator, or disabled control.
Use a local override when one interaction has a documented reason to wait longer, and pair it with an assertion for the precondition whenever possible.
const submit = page.getByRole("button", { name: "Submit order" });
await expect(page.getByTestId("price-validation"))
.toHaveText("Validated", { timeout: 15_000 });
await submit.click({ timeout: 5_000 });
await expect(page.getByRole("heading", { name: "Order confirmed" }))
.toBeVisible();The validation state owns the longer wait. The click remains a short browser action whose failure points to interactability.
Separate Navigation from Post-Navigation Readiness
Navigation timeout controls operations such as page.goto. It does not prove that a background report, websocket subscription, or dashboard calculation is complete. After navigation reaches its configured completion, assert the product state separately.
If navigation itself is slow, inspect DNS, server response, redirects, and resource loading in the trace. If navigation succeeds but the page remains in a loading state, the assertion or domain polling boundary owns the wait. Raising navigation timeout will not repair a stalled client request that starts afterward.
Set Assertion Windows by State Class
Most visible UI state should settle quickly. Jobs, exports, asynchronous indexing, and replicated data may have a longer documented window. Configure a normal assertion timeout and override only the matcher that observes the slower state.
Avoid chains of sequential long assertions that can each consume their full limit. Assert the primary readiness signal first, then use normal limits for dependent UI. For a group of values that become consistent together, one locator assertion or a bounded polling block may communicate the boundary more accurately than five independent long waits.
Account for Hooks and Teardown
An expensive beforeEach reduces the time left for the test. Move reusable, isolated provisioning to a worker fixture only when tests can safely share it. Keep per-test data reset visible and fast. beforeAll has a separate timeout, but shared mutable setup can make parallel and retry behavior harder to reason about.
Teardown must be idempotent and should fail with its own resource identity. A test timeout followed by another long cleanup failure produces confusing reports. Use server-side lease expiration or cleanup jobs for resources that must survive a terminated worker, because no in-process timeout policy can guarantee teardown after a hard crash.
Diagnose the Expired Layer
When a test timeout fires, inspect how much time setup and hooks consumed before assuming the final line is slow. When an action timeout fires, read the actionability call log for visibility, stability, event interception, enabled state, and locator resolution. When an assertion expires, compare the received value history and verify the observed signal is the correct one.
A navigation timeout points toward request and lifecycle evidence. A fixture timeout points toward provisioning or teardown. A global timeout accompanied by many similar failures points toward shared infrastructure or a retry storm. Preserve the trace, fixture logs, and service health evidence needed to make those distinctions.
Avoid Timeout Anti-Patterns
Hard sleeps spend the full delay even when the state is ready and still fail when it is slower. Global multiplication makes every defect expensive. Setting timeout: 0 removes a useful boundary and leaves the outer clock to produce a less specific failure. Copying one large number into test, action, navigation, and assertion settings prevents the expired clock from naming the stalled layer.
Retries are not additional wait strategy. They rerun a test in a fresh worker after failure. If a deterministic wait is undersized, fix that boundary; if the signal is wrong, a retry only changes timing around the same defect.
Review the Tradeoffs
Short budgets surface regressions quickly but can create false failures when an operation has a legitimate tail. Long budgets tolerate expected variability but increase feedback time and mask lost progress. Per-operation overrides improve diagnosis while adding policy complexity.
Choose values from observed healthy runs and documented product deadlines, then review outliers rather than tuning toward the slowest incident. Separate local developer ergonomics from CI resource protection where appropriate, but keep behavior close enough that failures reproduce in both places.
Operational Checklist
- Set a complete-run cap that leaves time for reporting and cleanup.
- Define one normal test timeout for the common workflow.
- Track every test-level exception and its reason.
- Give slow worker provisioning its own fixture timeout.
- Keep action limits below the test budget and inspect actionability failures.
- Treat navigation completion and application readiness as different states.
- Override only the assertion that owns a known slow domain boundary.
- Include hook and fixture setup time when reviewing test consumption.
- Preserve traces and service logs for each timeout class.
- Remove hard waits and unlimited clocks from release-gating tests.
Conclusion: Make the Expired Clock Name the Problem
Inventory the suite's clocks, assign each asynchronous boundary to one owner, and set the outer budgets before the inner ones. Keep ordinary actions and assertions tight, isolate legitimate fixture and job delays, and investigate every broad override. A timeout policy is working when a failure immediately narrows the search to provisioning, browser interaction, navigation, or product state.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Playwright documentation
Microsoft
Canonical API, locator, fixture, browser, and test-runner behavior.
- 02Playwright best practices
Microsoft
Official guidance for resilient tests, isolation, and user-facing locators.
- 03
FAQ / QUICK ANSWERS
Questions testers ask
What is the difference between Playwright test and assertion timeouts?
The test timeout bounds the test body, fixture setup, and beforeEach work, while an auto-retrying assertion has its own polling deadline. The test budget remains the outer limit even when an individual assertion allows more time.
Do Playwright fixtures need a separate timeout?
Most test-scoped fixtures can share the test budget. Give a fixture its own timeout when setup has a distinct, legitimately slower lifecycle, especially for worker-scoped provisioning, while keeping ordinary tests on a smaller budget.
How do actionTimeout and navigationTimeout differ?
Action timeout limits operations such as locator clicks, while navigation timeout limits navigation operations such as page.goto. Both are low-level controls and should remain below the outer test budget.
Should I increase the global timeout to fix a slow test?
No. Global timeout bounds the whole run and protects CI resources; it does not diagnose one scenario. Classify the stalled test, fixture, action, navigation, or assertion and change only the justified layer.
When should I call test.slow?
Use it for a test category that is intentionally slower by design and document why. Do not use it as a blanket response to intermittent delays, because it multiplies the test timeout without fixing the missing readiness signal.
RELATED GUIDES
Continue the learning route
GUIDE 01
Classify Flaky, Expected, and Failed Tests with Playwright Retries
Use Playwright retries, annotations, worker behavior, and result evidence to distinguish flaky tests, expected failures, and real regressions in CI.
GUIDE 02
Retrying Async State with expect.poll and expect.toPass in Playwright
Use Playwright expect.poll and expect.toPass for bounded asynchronous checks, with intentional intervals, idempotent probes, and useful failures.
GUIDE 03
Playwright Actionability Checks: Diagnose Clicks Before Using Force
Diagnose Playwright click failures with actionability checks, trial actions, overlay inspection, trace evidence, and disciplined use of force.
GUIDE 04
Read Playwright Trace Viewer Like a Failure Timeline
Analyze failed Playwright tests through action logs, DOM snapshots, requests, console evidence, and attachments instead of guessing from screenshots.