Smoketest

How to Fix Playwright TimeoutError

Playwright can report the same slow or missing element as an action, assertion, navigation, or test timeout, and changing the wrong setting wastes time. This guide maps each error string to its timeout, then shows how to fix the underlying wait instead of hiding it.

9 min read

Your test has stopped at a click with TimeoutError: locator.click: Timeout 30000ms exceeded. Or the last line says Test timeout of 30000ms exceeded., even though the click looked responsible. You searched the error because you need to know which clock expired and whether changing timeout will actually help.

Fix a Playwright TimeoutError by matching the exact error string to the action, assertion, navigation, test, or global timeout that expired. Then read the call log: correct a locator that never resolves, wait for a real precondition, remove a covering overlay, or make the locator unique. Raise the timeout only when the operation is genuinely slow.

This guide uses Playwright 1.61 behavior as of mid-2026. Start with the wording:

Error textTimeout classDefault in the JavaScript test runner
TimeoutError: locator.click: Timeout 30000ms exceeded.ActionNo separate action limit by default; 30 seconds is the library fallback
locator.click: Test timeout of 30000ms exceeded.Test, reported during an action30,000 ms
Error: expect(locator).toBeHidden() failed with a timeout fieldAssertion5,000 ms
page.goto: Timeout 30000ms exceeded.NavigationNo separate navigation limit by default; governed by the action-default chain or test deadline
Entire test run times outGlobalNo default

The distinction matters. A real-world report shows the first form for locator.click, while others show the test deadline attached to page.goto and browserContext.newPage (issue #34945, issue #33055, issue #41347).

How Playwright timeouts nest

Think of the limits as nested budgets. globalTimeout caps the complete run. Each test normally gets 30,000 ms. Actions, navigations, and assertions happen inside that test budget, but an assertion has its own 5,000 ms retry limit. A shorter inner limit usually reports first. The outer test deadline can still interrupt an operation whose own timeout is longer (Playwright timeout documentation).

beforeEach and fixture setup consume the test's budget. beforeAll, afterAll, and worker teardown get separate budgets. A fixture can also declare its own timeout through test.extend. This is why adding 30 seconds to one click does not guarantee another 30 seconds if the enclosing test has almost no time left.

In the JavaScript test runner, actions have no independent timeout by default. They can run until the 30-second test deadline. In library mode, Playwright's fallback chain is an explicit per-call option, then a configured default, then the parent default, then 30,000 ms. Do not read a JavaScript API note saying the option defaults to zero as proof that a library script can wait forever.

Fix locator action timeouts

The headline locator error is:

Text
TimeoutError: locator.click: Timeout 30000ms exceeded.

A click waits for one matching element that is visible, stable, able to receive events, and enabled. Playwright defines stable as keeping the same bounding box for at least two consecutive animation frames. These checks explain why a locator can resolve while click() still times out (Playwright actionability documentation).

Set an action limit for the project when you want clicks and similar operations to fail before the test budget expires.

playwright.config.ts

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    actionTimeout: 10_000,
  },
});

Override it for one known-slow action when the extra time belongs to that operation.

tests/report.spec.ts

TypeScript
await page.getByRole('button', { name: 'Generate report' }).click({
  timeout: 30_000,
});

The call log is the diagnosis. If it never reports a resolved element, check the selector, page state, and whether an iframe needs frameLocator. More time cannot make the wrong locator match.

Read the call log before changing config

Playwright's action log tells you which actionability check is blocking the click. These lines map to different fixes (Playwright source for DOM actions):

Call-log lineWhat it meansFix
waiting for element to be visible, enabled and stableThe element is missing or fails an actionability checkConfirm the page state, then assert the needed state
element is not visibleIt exists but has no visible box or is hiddenWait for the UI condition that reveals it
element is outside of the viewportPlaywright cannot reach the intended target as expectedCheck layout and the selected element
intercepts pointer eventsAnother element covers the targetDismiss the overlay or wait for it to disappear
Element is not attached to the DOMThe page replaced the node during the actionLocate from stable page state and wait for the replacement
retrying click actionThe failed actionability check is repeatingFix the check named immediately before this line

For a covered button, wait for the overlay's real exit condition.

tests/checkout.spec.ts

TypeScript
const overlay = page.getByTestId('loading-overlay');
await expect(overlay).toBeHidden();
await page.getByRole('button', { name: 'Pay' }).click();

force: true bypasses actionability checks. It can be useful when bypassing them is exactly what the test intends, but it is the wrong repair for a user-facing button covered by an overlay. It turns a real interaction defect into a passing test.

If an animation keeps the element unstable, wait for the application state that marks the animation complete. Avoid replacing that condition with page.waitForTimeout(). Playwright documents fixed waits as a debugging aid, not a reliable synchronization strategy.

Fix expect assertion timeouts

Assertions have a separate 5,000 ms timeout. Since Playwright 1.55, a failed web-first assertion starts with a message such as:

Text
Error: expect(locator).toBeHidden() failed

It is followed by structured locator, expected, received, timeout, and call-log information. In Playwright 1.54 and earlier, the common form was:

Text
Error: Timed out 5000ms waiting for expect(locator).toBeHidden()

The format changed in Playwright 1.55, not the underlying need to inspect the assertion and its retries (Playwright PR #36543).

Use a project-wide assertion timeout when your application normally needs more than five seconds to settle.

playwright.config.ts

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    timeout: 10_000,
  },
});

Prefer a per-assertion override when one operation, such as report generation, is the exception.

tests/report.spec.ts

TypeScript
await expect(page.getByText('Report ready')).toBeVisible({
  timeout: 30_000,
});

An assertion timeout is independent of the configured test timeout, but it still runs inside the test. Giving an assertion 30 seconds does not extend a test with two seconds remaining.

Fix Playwright waitFor timeouts

Use locator.waitFor() when you need a state transition without making an assertion. It can wait for attached, detached, visible, or hidden. Hidden includes a detached element, an empty bounding box, or visibility:hidden (Playwright API parameters).

tests/orders.spec.ts

TypeScript
const orderSent = page.locator('#order-sent');
await orderSent.waitFor({ state: 'visible', timeout: 15_000 });

Choose the state explicitly so the code says what transition matters. If the element never appears, verify the locator and the action that should create it. If it appears late because a real backend job takes 12 seconds, a 15-second per-call timeout is reasonable. If the delay comes from a missing await or a race, a larger timeout only makes the failure slower.

Fix navigation timeouts

A navigation failure commonly reads:

Text
page.goto: Timeout 30000ms exceeded.

Its call log includes the destination and the lifecycle event it is waiting for. JavaScript test-runner navigation has no separate limit by default. You can set navigationTimeout, and navigation-specific defaults take precedence over general default timeouts.

playwright.config.ts

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    navigationTimeout: 30_000,
  },
});

tests/home.spec.ts

TypeScript
await page.goto('https://example.com', { timeout: 45_000 });

Before raising it, check whether the server is reachable and which lifecycle event the call log names. page.goto() throws for a timeout, invalid URL, SSL error, unreachable server, or main-resource load failure. It does not throw merely because the response is a valid HTTP 404 or 500, so assert the response or page outcome separately when status matters (Playwright page navigation documentation).

Fix test and global timeouts

The default test error is exact and short:

Text
Test timeout of 30000ms exceeded.

Hook and fixture variants name the phase, for example Test timeout of 30000ms exceeded while running "beforeEach" hook. or Test timeout of 30000ms exceeded while setting up "fixtureName". The named phase tells you where the shared budget went (Playwright timeout manager source).

Raise the project test budget at the top level. Set a single test's budget with test.setTimeout(). test.slow() triples the default.

playwright.config.ts

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 60_000,
  globalTimeout: 3_600_000,
});

tests/export.spec.ts

TypeScript
import { test } from '@playwright/test';

test('exports a large report', async ({ page }) => {
  test.setTimeout(120_000);
  await page.goto('/reports');
});

Global timeout has no default. It caps the entire test run, including all projects, retries, and workers. Keep it as a suite-level circuit breaker, not as the first setting you change for a failing click. Browser launch has another separate default limit of 180,000 ms.

Fix strict-mode ambiguity

Strict-mode failure is not itself a timeout. A single-element operation throws when its locator matches multiple elements, beginning with strict mode violation. Ambiguity inside a retried assertion or wait can still present as a timeout-adjacent failure.

The fix is a locator that identifies one intended element. Playwright supports first(), last(), and nth(), but its locator guide does not recommend using them as the default answer because page changes can silently select a different element (Playwright locator strictness documentation).

tests/settings.spec.ts

TypeScript
await page
  .getByRole('dialog', { name: 'Delete project' })
  .getByRole('button', { name: 'Delete' })
  .click();

Scope by a unique dialog, row, label, or accessible name. Use first() only when choosing the first match is genuinely part of the behavior under test.

When raising the timeout is correct

Raise a timeout when you can name the operation that legitimately needs the time: a report-generation job, a cold staging start, or measured CI contention. Use the narrowest override that matches the cause. A slow assertion gets an assertion override. A slow navigation gets a navigation override. A test with several expected long phases gets a test budget.

CI-only configuration is appropriate when CI consistently has less CPU or slower services than local development.

playwright.config.ts

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: process.env.CI ? 60_000 : 30_000,
  expect: {
    timeout: process.env.CI ? 10_000 : 5_000,
  },
});

Do not raise a timeout when the call log points to a wrong selector, missing page transition, permanent overlay, unstable animation, disabled control, or strict-mode ambiguity. Those causes do not improve with time. A larger number delays the same failure and can hide a product defect.

For Python, Java, and .NET, action APIs default to 30,000 ms. Python can change the page or context default with set_default_timeout, while navigation-specific setters win for navigation. That differs from the JavaScript test runner's default of no independent action limit, so copy timeout advice across languages carefully (Playwright actionability documentation).

FAQ

What does timeout 30000ms exceeded mean?

It means a 30-second budget expired, but the exact prefix identifies the budget. Test timeout of 30000ms exceeded is the enclosing test deadline. locator.click: Timeout 30000ms exceeded is an action deadline or library fallback. Read the call log before changing config because both strings can appear at the same click.

How to fix timeout error in Playwright?

Match the error to its timeout class, then inspect the call log. Fix a locator that never resolves, assert the state that makes a late element ready, wait for a covering overlay to disappear, or make an ambiguous locator unique. Increase only the narrow timeout for an operation that is expected to be slow.

What is the recommended timeout for Playwright?

There is no single recommended number. Playwright 1.61 defaults tests to 30,000 ms and assertions to 5,000 ms. Keep those until measured application behavior justifies a change. Prefer per-call overrides for exceptional slow work, and use project config only when the slower budget applies consistently across the suite.

How do I fix locator.click timeout in Playwright?

Read the lines before retrying click action. No resolved element suggests a wrong locator or page state. intercepts pointer events means an overlay covers the target. Visibility, stability, or enabled failures name other causes. Fix that condition. Use force: true only when bypassing user-facing actionability is the test's explicit intent.

How to change default timeout in Playwright Python?

Python action APIs default to 30,000 ms. Change the page or browser-context action default with set_default_timeout, and use the navigation-specific default setter when navigation needs a different budget. A per-call timeout remains the narrower choice for one slow operation. Check the failing call first so a larger default does not mask a permanent condition.

Share this post

The QA column is no longer where the sprint goes to die.

Move one ticket. Watch it come back tested. Then decide.

Keep reading

All posts →