QA How-To
How to Fix Playwright Timeout of 30000ms Exceeded
Learn how to fix Playwright timeout 30000ms exceeded errors by finding slow steps, improving waits, correcting locators, and setting precise timeout budgets.
18 min read | 2,618 words
TL;DR
To fix Playwright timeout 30000ms exceeded, find the last pending operation in the error call log or trace, then correct its locator, application state, network dependency, or missing await. Increase the test timeout only when the workflow is legitimately longer than 30 seconds, and configure assertion, action, navigation, hook, or fixture timeouts separately when that is the budget that actually needs adjustment.
Key Takeaways
- Read the call log and trace to identify whether the test, assertion, action, navigation, hook, or fixture consumed the budget.
- Fix incorrect locators, missing awaits, blocked actionability, and unfinished application requests before increasing a timeout.
- Use web-first locators and auto-retrying assertions instead of hard waits and fragile DOM selectors.
- Set a larger timeout only at the narrowest level that represents a legitimate service-level expectation.
- Capture traces, screenshots, console messages, and failed requests on retries so CI-only failures have evidence.
- Keep tests isolated and move expensive shared setup into authenticated storage state or a properly scoped fixture.
When you need to fix playwright timeout 30000ms exceeded, do not begin by changing timeout: 120_000 everywhere. The message means the Playwright Test runner exhausted the test's 30-second budget, but the reason is usually a locator waiting for the wrong element, an actionability check that never passes, a slow setup hook, a stalled request, or a promise that was not awaited correctly.
The fastest reliable fix is to identify the last operation still pending, reproduce it with a trace, and correct the underlying synchronization or application-state problem. This guide explains the timeout layers, root causes, diagnostic workflow, and targeted configuration changes that make a suite faster and more trustworthy.
TL;DR
| Symptom | First evidence to inspect | Preferred fix |
|---|---|---|
| Whole test ends at about 30 seconds | Test duration, hooks, fixture steps | Remove wasted work or set a justified per-test budget |
expect(...) call log shows 5 seconds |
Assertion call log | Correct the locator or set an assertion-specific timeout |
| Click waits while element is covered | Trace actionability log and screenshot | Remove the overlay or wait for the real ready state |
page.goto() stalls |
Network panel, failed requests, load state | Fix the URL or dependency and use the correct navigation boundary |
| Only CI fails | Retry trace, worker load, environment logs | Stabilize data and infrastructure, then tune CI-only budgets if justified |
A practical order is: reproduce, read the last waiting step, inspect the trace, verify the locator and page state, replace hard waits, check network failures, and only then adjust the narrowest applicable timeout.
1. What the Error Actually Means
The exact test-timeout message is:
Timeout of 30000ms exceeded.
Playwright Test applies a default 30,000 ms timeout to each test. That budget includes the test function, fixture setup, and time spent in beforeEach hooks. When the budget expires, the runner interrupts the test even if an individual action would otherwise keep waiting. Teardown receives a separate budget after the test function finishes, while beforeAll and afterAll hooks have their own timeout with the same default value.
This message is different from a locator action timeout or an assertion timeout. A failed assertion usually includes text such as expect.toHaveText with timeout 5000ms. A navigation may mention page.goto, and a locator action can report that it was waiting for an element to become visible, enabled, stable, or able to receive events. The last call log entry is therefore more useful than the headline error.
Think of the 30 seconds as a test-level deadline, not a statement that the browser was idle for exactly 30 seconds. Several individually reasonable operations can consume the deadline together. A 12-second login, a 10-second data setup, and a 9-second checkout assertion are enough to fail the test. Your diagnosis must account for the complete timeline.
2. Timeout Types You Must Distinguish
Playwright exposes multiple timeout scopes because they represent different expectations. Setting the wrong one either has no effect or hides the real bottleneck.
| Timeout | What it limits | Typical configuration |
|---|---|---|
| Test | Test body, fixture setup, and beforeEach |
Top-level timeout or test.setTimeout() |
| Expect | One auto-retrying assertion | expect.timeout or assertion option |
| Action | A click, fill, check, or similar action | use.actionTimeout or action option |
| Navigation | Navigation actions such as page.goto() |
use.navigationTimeout or navigation option |
| Hook | One beforeAll or afterAll |
test.setTimeout() inside the hook |
| Fixture | One fixture setup or teardown | Fixture tuple option timeout |
| Global | Entire test run | globalTimeout |
A common trap is increasing expect.timeout when the entire test is timing out. The larger assertion allowance cannot extend the enclosing test deadline. The reverse is also true: a 60-second test still fails a default assertion after its own shorter expectation budget.
Use different numbers only when they encode a meaningful contract. For example, an order confirmation may have a 15-second product requirement, while ordinary UI actions should settle in 5 seconds. A dedicated assertion communicates that distinction. Review the Playwright locator strategy guide when a timeout points to selector design rather than execution time.
3. Root Causes When You Fix Playwright Timeout 30000ms Exceeded
Most failures fall into a small set of root-cause groups:
- Wrong locator: The selector matches nothing, matches multiple elements under strict mode, or depends on unstable markup.
- Failed prerequisite: Login, navigation, test-data creation, or a prior click did not complete, so the expected page was never reached.
- Actionability never becomes true: The target stays hidden, disabled, animated, detached, or covered by another element.
- Network or backend delay: An API request hangs, returns an error, retries repeatedly, or leaves the interface in a loading state.
- Incorrect async control: A missing
await, unreturned promise, accidental sequential loop, or unresolved custom promise distorts the flow. - Oversized test: One case performs many business journeys, repeated logins, or expensive setup that belongs elsewhere.
- Environment contention: CI workers compete for CPU, memory, database locks, accounts, or rate-limited services.
- Inappropriate waiting:
waitForTimeout()calls consume the deadline but prove no useful state.
The error location may be downstream from the true cause. If the login submit request failed, the timeout might appear on getByRole('heading', { name: 'Dashboard' }). Treat every timeout as a broken state transition: identify the state you had, the state you expected, and the observable event that should connect them.
4. Step-by-Step Diagnostic Workflow
Start with one failing test and preserve its evidence. Run it by title with a single worker and a visible browser when possible:
npx playwright test tests/checkout.spec.ts --grep "places an order" --workers=1 --headed
Then follow this sequence:
- Read the full stack and call log. Record the last Playwright API call that was waiting.
- Confirm the page URL, visible heading, signed-in user, and test-data identifiers at that moment.
- Open the trace and inspect the action snapshot before and after the pending step.
- Check whether the locator resolves to zero, one, or multiple elements.
- Review console errors, failed network requests, response codes, and endless loading indicators.
- Compare local and CI durations. A failure only under parallel load points toward shared data or capacity.
- Temporarily add a focused assertion before the failing action to identify the earliest broken state.
- Apply one root-cause fix, then repeat the test enough times to expose intermittent behavior.
Enable useful retry evidence in configuration:
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0,
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
A retry is a diagnostic opportunity, not proof that a flaky test is healthy. Use the first failure and retry trace together to find the differing state.
5. Fix Locators and Actionability Before Timeouts
Playwright locators re-resolve the DOM and automatically wait for actionability. For a click, the element generally must resolve uniquely, be visible, stable, enabled, and able to receive pointer events. If any check remains false, extending the timeout only waits longer for the same impossible condition.
Prefer user-facing locators:
import { test, expect } from '@playwright/test';
test('submits a support request', async ({ page }) => {
await page.setContent(`
<form>
<label for="email">Work email</label>
<input id="email" type="email">
<button type="submit">Send request</button>
<p role="status" hidden>Request sent</p>
</form>
<script>
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
document.querySelector('[role=status]').hidden = false;
});
</script>
`);
await page.getByLabel('Work email').fill('qa@example.com');
await page.getByRole('button', { name: 'Send request' }).click();
await expect(page.getByRole('status')).toHaveText('Request sent');
});
This complete test runs without an external site. It uses label, role, and status semantics instead of a CSS path. If a cookie banner covers the button, model the actual user behavior by accepting or closing it. Avoid force: true unless the test specifically intends to bypass a nonessential hit-target check. Forced clicks can report success while the application receives no meaningful interaction.
6. Replace Hard Waits With Observable Conditions
A fixed delay is both too long when the system is fast and too short when it is slow. Ten calls to page.waitForTimeout(1000) spend a third of the default test budget without validating a single outcome. Production tests should wait for a state that matters to the user or business flow.
Replace this:
await page.getByRole('button', { name: 'Create report' }).click();
await page.waitForTimeout(5000);
await page.locator('.report-row:first-child').click();
With an explicit outcome:
await page.getByRole('button', { name: 'Create report' }).click();
const report = page.getByRole('row', { name: /Quarterly report/ });
await expect(report).toBeVisible({ timeout: 15_000 });
await report.getByRole('link', { name: 'Open' }).click();
For application readiness, prefer a stable UI assertion, a specific response associated with the action, or a domain state. Do not reflexively wait for networkidle. Applications with analytics, polling, web sockets, or background refreshes may never become globally idle even though the page is ready for the user. The end-to-end synchronization guide explains how to choose signals that reflect business completion.
7. Synchronize Network-Driven Work Correctly
When a click triggers a request, establish the response wait before the click so a fast response cannot race past the listener:
import { test, expect } from '@playwright/test';
test('waits for the order API outcome', async ({ page }) => {
await page.goto('/checkout');
const orderResponsePromise = page.waitForResponse(response =>
response.url().endsWith('/api/orders') &&
response.request().method() === 'POST'
);
await page.getByRole('button', { name: 'Place order' }).click();
const orderResponse = await orderResponsePromise;
expect(orderResponse.ok()).toBeTruthy();
await expect(page.getByRole('heading', { name: 'Order confirmed' }))
.toBeVisible();
});
Promise.all is also valid when the action and event should be initiated together, but never add a blind response wait for every click. Playwright actions already wait for navigation they initiate where applicable. Add network synchronization only when the response itself is the contract or when the UI lacks a sufficient ready signal.
If the request never finishes, inspect its URL, method, payload, authentication, and response. A route mock that does not call route.continue(), route.fulfill(), or route.abort() can also stall the page. Backend logs may reveal deadlocks or invalid test data that browser evidence cannot explain.
8. Fix Playwright Timeout 30000ms Exceeded With Precise Configuration
After correcting waste and synchronization, set policy-level budgets in playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 45_000,
expect: {
timeout: 8_000,
},
globalTimeout: process.env.CI ? 60 * 60_000 : undefined,
use: {
baseURL: 'http://127.0.0.1:3000',
actionTimeout: 10_000,
navigationTimeout: 30_000,
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
These numbers are examples, not universal targets. Base them on application expectations and observed healthy duration. An action timeout shorter than the test timeout helps fail close to a stuck action while leaving budget for cleanup and assertions.
For a single legitimately slow workflow, use a local override:
test('exports a large audited report', async ({ page }) => {
test.setTimeout(90_000);
await page.goto('/reports');
await page.getByRole('button', { name: 'Generate export' }).click();
await expect(page.getByText('Export ready')).toBeVisible({ timeout: 60_000 });
});
Keep the reason visible in the test name or a comment. A local exception is easier to review than a global timeout that silently relaxes every test.
9. Handle Hooks and Fixtures Without Hiding Slowness
Expensive setup often consumes the test deadline before the first test line runs. A beforeEach login through the UI repeated across dozens of tests is a common example. Authenticate once in a setup project and reuse storage state when the scenarios do not require testing login itself. Keep each test's business data isolated.
A slow worker fixture can receive its own timeout:
import { test as base } from '@playwright/test';
type WorkerFixtures = { seededTenant: string };
export const test = base.extend<{}, WorkerFixtures>({
seededTenant: [async ({ request }, use) => {
const response = await request.post('/api/test/tenants', {
data: { plan: 'trial' },
});
if (!response.ok()) {
throw new Error('Tenant seed failed: ' + response.status());
}
const tenant = await response.json();
await use(tenant.id);
}, { scope: 'worker', timeout: 60_000 }],
});
This configuration keeps fixture policy explicit. It does not excuse an unreliable seed endpoint. Log its duration, validate the response, and clean up deterministically. If a beforeAll is genuinely slow, set its timeout within that hook, but avoid coupling tests through mutable shared records. The Playwright fixture design patterns guide covers ownership and scope in more detail.
10. Find Missing Awaits and Async Control Bugs
Playwright APIs return promises. Missing await can allow the test to move into an invalid state, generate unhandled rejection noise, or finish while work is still pending. Static lint rules such as @typescript-eslint/no-floating-promises help catch the defect before runtime.
Incorrect:
page.getByRole('button', { name: 'Save' }).click();
expect(page.getByRole('status')).toHaveText('Saved');
Correct:
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('status')).toHaveText('Saved');
Also inspect loops. forEach does not await an async callback in sequence:
for (const name of ['Ada', 'Linus', 'Grace']) {
await page.getByLabel('Name').fill(name);
await page.getByRole('button', { name: 'Add' }).click();
await expect(page.getByRole('listitem', { name })).toBeVisible();
}
Do not use Promise.all to operate several elements on the same page unless concurrent interactions are genuinely safe. Browser UI actions usually describe an ordered user journey. Concurrency is more appropriate for independent API setup, and even there it must respect rate limits and data dependencies.
11. Diagnose CI-Only Timeout Failures
A test that passes locally and times out in CI is not automatically fixed by multiplying all timeouts. CI can expose shared-state races, resource starvation, slower service dependencies, missing fonts, different locale, proxy issues, or a server that was never ready.
Compare these facts between environments:
- Browser and Playwright versions from the lockfile and install logs.
- CPU and memory limits, worker count, and test shard size.
- Base URL, feature flags, locale, timezone, and authentication configuration.
- Response duration and status for the operation preceding the timeout.
- Uniqueness of users, tenants, orders, and other mutable records.
- Trace screenshots and DOM snapshots at the same action.
Reduce workers temporarily to test the contention hypothesis:
npx playwright test --workers=1
If that fixes the suite, do not keep one worker as the only answer. Identify the shared account, database lock, rate limit, or capacity constraint. When CI is predictably slower but otherwise correct, a small CI-specific test budget can be reasonable. Preserve a tighter action and assertion timeout so stuck operations still fail with useful diagnostics. Use the flaky test root-cause workflow to turn intermittent failures into owned defects.
12. Verify the Fix and Prevent Regression
A timeout fix is complete when it survives repetition, parallel execution, and the environment where it originally failed. Run the focused test repeatedly, then its containing file, then the relevant project matrix. Avoid treating one green retry as sufficient evidence.
Useful verification commands include:
npx playwright test tests/checkout.spec.ts --grep "places an order" --repeat-each=20
npx playwright test tests/checkout.spec.ts --workers=4
npx playwright test --project=chromium
Inspect the duration distribution rather than only pass rate. A test that normally takes 3 seconds but occasionally takes 29 seconds remains risky even if it passes. The trace should show a clear sequence of state transitions and no unexplained idle gap.
Add prevention at the suite level: lint floating promises, forbid production uses of waitForTimeout through code review or linting, keep traces on the first retry, and report slow tests. Establish ownership for service dependencies and test data. Where a product operation has a formal performance objective, align the relevant assertion timeout with that objective and fail with a domain-specific message.
Finally, remove temporary debug logging and excessively broad timeout overrides. Keep evidence capture that will help with future failures, but leave the test readable enough that another engineer can understand why each wait exists.
Interview Questions and Answers
Q: What does Timeout of 30000ms exceeded. mean in Playwright?
It means the Playwright Test test-level budget expired. The budget includes the test body, fixture setup, and beforeEach time, so the line shown at failure may only be the operation that happened to be pending when the deadline arrived.
Q: Why does increasing the expect timeout not fix a 30-second test timeout?
Assertion timeout and test timeout are nested but separate. A longer assertion allowance cannot extend the enclosing test deadline. Increase the test budget only if the complete workflow legitimately needs it.
Q: How do you debug a click that times out?
Inspect the call log and trace for locator count and actionability. Verify that the element is uniquely resolved, visible, stable, enabled, and unobscured, then check whether a prior state transition failed.
Q: Should a test use waitForTimeout() to stabilize asynchronous UI?
No. A fixed sleep neither proves readiness nor adapts to system speed. Wait for a user-visible state, a specific response, or another domain condition.
Q: When is test.setTimeout() appropriate?
Use it for an exceptional test whose valid business flow is measurably longer than the suite default, such as a large report export. Keep the override local and document the reason.
Q: How should timeout handling differ in CI?
Capture trace and failure artifacts, compare environment capacity, and test for data contention by reducing workers. A modest CI-specific budget is acceptable only after shared-state and infrastructure causes are addressed.
Common Mistakes
- Raising every timeout to several minutes before reading the call log.
- Using CSS chains or positional XPath for elements that have stable roles, labels, or test IDs.
- Adding
waitForTimeout()after every navigation or click. - Waiting for global network idleness in an application that polls continuously.
- Forcing clicks that are blocked by a real overlay users must handle.
- Sharing one mutable account or record across parallel tests.
- Forgetting
awaiton actions and Playwright assertions. - Treating a retry pass as a fix instead of inspecting the first failure.
- Setting an assertion budget longer than the enclosing test budget without considering the full timeline.
- Keeping repeated UI login and data creation in
beforeEachwhen faster isolated setup is available.
Conclusion
To fix playwright timeout 30000ms exceeded reliably, identify which operation consumed the test-level deadline and repair the failed state transition. Correct locators, application readiness, async control, network behavior, and fixture scope before changing configuration.
When a longer workflow is legitimate, apply a measured timeout at the narrowest level and verify the result under repetition and CI parallelism. The goal is not merely a green run. It is a test that fails quickly for the right reason and leaves enough evidence to act.
Interview Questions and Answers
What is included in Playwright's default 30-second test timeout?
The test body, fixture setup, and beforeEach hooks share the test's timeout budget. Teardown gets a separate budget after the test finishes, while beforeAll and afterAll have their own hook timeout.
How would you investigate a Timeout of 30000ms exceeded failure?
I start with the last pending API call in the call log, then inspect the trace snapshots, locator resolution, console, and failed requests. I verify the previous business state transition because the visible timeout is often downstream from the original failure.
Why is globally increasing the timeout a weak first response?
It makes every genuine failure slower and can hide incorrect locators, stalled requests, or shared-state races. A strong fix addresses the cause and changes only the budget that represents a legitimate expectation.
What is the difference between auto-waiting and auto-retrying assertions?
Auto-waiting applies actionability checks before actions such as click. Auto-retrying assertions repeatedly evaluate a condition until it passes or the assertion timeout expires.
How do you replace a hard wait in Playwright?
I identify the observable outcome the delay was trying to cover, such as a visible status, enabled control, specific response, or changed URL. I wait for or assert that state directly so the test proceeds as soon as the application is ready.
When would you configure a fixture timeout?
I use a fixture-specific timeout when a legitimate fixture, often worker-scoped environment provisioning, has a different lifecycle and budget from a test. I still validate its response and duration so the separate allowance does not conceal an unreliable dependency.
How do you prove a timeout fix is stable?
I repeat the focused test, run it with production-like worker concurrency, and reproduce it in the original CI environment. I also inspect durations and traces to confirm there is no unexplained idle period near the limit.
Frequently Asked Questions
How do I fix Timeout of 30000ms exceeded in Playwright?
Read the final waiting operation in the call log, inspect its trace, and verify the locator, page state, network request, and awaited promises. Increase the test timeout only if the complete workflow is legitimately longer than 30 seconds.
How can I increase the timeout for one Playwright test?
Call test.setTimeout(milliseconds) inside that test. Keep the override local and choose a value based on the expected duration of the business operation rather than an arbitrary large number.
What is the difference between Playwright test timeout and expect timeout?
The test timeout limits the full test body plus relevant setup time. The expect timeout limits one auto-retrying assertion and cannot extend the outer test deadline.
Why does a Playwright locator keep timing out?
It may match no element, match the wrong element, or never satisfy visibility, stability, enabled, or event-receiving checks. Inspect the trace and prefer role, label, text, or stable test ID locators.
Should I use page.waitForTimeout to stop Playwright flakiness?
No. Fixed sleeps waste time and still fail when the application takes longer than expected. Wait for an observable UI, API, or business state instead.
Why does my Playwright test time out only in CI?
CI can reveal CPU contention, excessive workers, shared test data, rate limits, missing configuration, or slower dependencies. Compare traces and environment logs, then test the contention theory with one worker before deciding whether a CI-specific budget is justified.
Does Playwright automatically wait before clicking?
Yes. Locator actions perform relevant actionability checks, including unique resolution, visibility, stability, enabled state, and the ability to receive events. They still time out if the application never reaches those conditions.
Related Guides
- How to Fix Playwright Test timeout of 30000ms exceeded
- How to Fix Playwright waiting for element to be visible enabled and stable
- How to Fix Playwright element is outside of the viewport
- How to Fix Playwright locator resolved to hidden element
- How to Fix Playwright browserType.launch executable does not exist
- How to Fix Playwright element is not visible