ScreenshotNeo

BlogHow-to

Playwright Timeout: How to Change and Fix It

Identify the Playwright timeout scope, change the right setting, and fix flaky waits with runnable TypeScript examples and troubleshooting.

By the ScreenshotNeo team29 September 20268 min read

Playwright Timeout: How to Change and Fix It

Direct answer: Playwright does not have one universal timeout. Identify what timed out, then change that scope: the test (30 seconds by default), an auto-retrying assertion (5 seconds), an action, navigation, fixture, hook, or the whole run. Raise only the limit that matches the slow operation, and fix the wait condition when the application is not becoming ready.

This guide shows every timeout scope, precedence rules, complete TypeScript examples, and a troubleshooting process for “How do I increase the timeout in Playwright?” and “Why does my Playwright assertion time out?” The defaults and APIs below follow the official Playwright Timeouts guide and the Page API.

1. Find the timeout scope first

Read the error and call log before editing configuration. The operation named in the message usually identifies the setting you need.

A navigation timeout and an assertion timeout protect different stages of the same test.
A navigation timeout and an assertion timeout protect different stages of the same test.
Scope Documented default Change it with What it covers
Test 30,000 ms timeout, test.setTimeout() Test function, fixture setup, and beforeEach
Assertion 5,000 ms expect.timeout or assertion timeout Retry period for auto-retrying assertions
Action No test-runner default use.actionTimeout or action option Clicks, fills, checks, and other locator actions
Navigation No test-runner default use.navigationTimeout, page/context default, or navigation option goto, reload, waitForURL, and related calls
Fixture No fixture-specific default Fixture timeout Setup/teardown for a custom fixture
beforeAll/afterAll 30,000 ms in the guide test.setTimeout() inside the hook That hook, separately from each test
Whole run No global default globalTimeout Entire test command

A test timeout is separate from an assertion timeout. Making a test 2 minutes long does not make a 5-second assertion retry for 2 minutes. Navigation defaults also have their own precedence: a page-level navigation timeout takes priority over a page default timeout, and page settings take priority over context settings.

2. Set a project-wide test timeout

Use playwright.config.ts when most tests legitimately need the same ceiling.

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

export default defineConfig({
  timeout: 60_000,
  expect: { timeout: 10_000 },
  globalTimeout: 15 * 60_000,
});

timeout limits each test. Time spent in fixture setup and beforeEach counts toward it. After the test function ends, afterEach and fixture teardown receive an additional timeout of the same value. globalTimeout stops the complete run, which is useful in CI to prevent a wedged worker from running forever.

3. Extend one slow test

Keep an exceptional test local so unrelated tests retain a useful failure signal.

import { test, expect } from '@playwright/test';

test('exports a large report', async ({ page }) => {
  test.setTimeout(120_000);
  await page.goto('https://example.com/reports');
  await page.getByRole('button', { name: 'Export' }).click();
  await expect(page.getByText('Export complete')).toBeVisible();
});

When a test is predictably slow and tripling its normal limit is appropriate, test.slow() is a concise alternative. In a hook, the official guide shows extending the current budget rather than replacing it:

test.beforeEach(async ({}, testInfo) => {
  testInfo.setTimeout(testInfo.timeout + 30_000);
  // slow setup
});

Hook timeouts are separate from an individual test’s hook execution. For beforeAll or afterAll, call test.setTimeout() inside that hook.

4. Fix assertion timeouts

Locator assertions such as toBeVisible, toHaveText, and toHaveURL retry until they pass or their assertion timeout expires. Configure all assertions or one assertion:

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

export default defineConfig({
  expect: { timeout: 10_000 },
});
await expect(page.getByRole('status')).toHaveText('Ready', {
  timeout: 15_000,
});

If the assertion still fails at the larger limit, inspect the locator and application state. A longer retry period cannot make a wrong selector match.

5. Set action timeouts

Use an action default for a project-wide interaction budget, or override only the slow operation.

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

export default defineConfig({
  use: {
    actionTimeout: 10_000,
  },
});
await page.getByRole('button', { name: 'Generate' }).click({
  timeout: 20_000,
});
await page.locator('#email').fill('dev@example.com', { timeout: 5_000 });

Actions auto-wait for an element to be attached, visible, stable, enabled, and able to receive events. If one condition never becomes true, diagnose that condition instead of setting every action to minutes.

6. Set navigation timeouts and choose readiness correctly

Configure navigation separately from actions:

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

export default defineConfig({
  use: {
    navigationTimeout: 30_000,
  },
});
await page.goto('https://example.com/dashboard', {
  timeout: 45_000,
  waitUntil: 'domcontentloaded',
});
await page.waitForURL('**/dashboard', { timeout: 20_000 });

The Page API documents load as the default and also supports domcontentloaded, commit, and networkidle. Playwright marks networkidle as discouraged for testing because analytics, WebSockets, and polling can keep a page busy indefinitely. Most explicit waitForLoadState() calls are unnecessary because Playwright waits before actions. Navigate, then assert the application state you actually need:

await page.goto('https://example.com/app');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('data-table')).toContainText('January');

For a page or context default:

page.setDefaultTimeout(10_000);
page.setDefaultNavigationTimeout(30_000);
// Context equivalents can be set before creating pages.
context.setDefaultTimeout(10_000);
context.setDefaultNavigationTimeout(30_000);

page.setDefaultNavigationTimeout() wins over the page default for navigation methods. A documented timeout: 0 disables the relevant Page or BrowserContext timeout; use it only for a deliberately unbounded operation because a hung page can hang the run.

7. Give a slow fixture its own budget

A custom fixture can be slow without making every test slow.

import { test as base } from '@playwright/test';

export const test = base.extend<{ seededUser: string }>({
  seededUser: [async ({}, use) => {
    const userId = await seedDatabase();
    await use(userId);
    await deleteUser(userId);
  }, { timeout: 60_000 }],
});

async function seedDatabase(): Promise<string> {
  return 'user-123';
}
async function deleteUser(_id: string): Promise<void> {}

The fixture timeout applies to setup and teardown for that fixture. Keep the test timeout independent so a database outage fails with a clear fixture error.

8. A complete, runnable example

This small project combines the common scopes. Save it as playwright.config.ts and tests/dashboard.spec.ts, then run npx playwright test.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 60_000,
  expect: { timeout: 8_000 },
  globalTimeout: 10 * 60_000,
  use: {
    actionTimeout: 10_000,
    navigationTimeout: 30_000,
    trace: 'retain-on-failure',
  },
});

// tests/dashboard.spec.ts
import { test, expect } from '@playwright/test';

test('dashboard loads data', async ({ page }) => {
  await page.goto('https://example.com/dashboard', {
    waitUntil: 'domcontentloaded',
  });
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await expect(page.getByTestId('data-table')).toContainText('January', {
    timeout: 15_000,
  });
  await page.getByRole('button', { name: 'Refresh' }).click({ timeout: 5_000 });
});

Replace the example URL and locators with your application. The configuration is intentionally explicit so a failure tells you whether the test, assertion, action, or navigation budget expired.

9. Diagnose before raising a limit

  1. Read the call log. It names the locator, URL, or assertion that was waiting.
  2. Run one test with a trace. Use npx playwright test tests/dashboard.spec.ts --trace on, then inspect the action timeline and network activity.
  3. Check the condition. Verify the selector, route, feature flag, authentication state, and test data. A timeout often means the expected state never exists.
  4. Choose the narrowest fix. Use an operation-level option for one slow call, a test timeout for one slow test, and config only for a suite-wide policy.
  5. Assert readiness directly. Prefer a visible heading, enabled button, URL, or response-backed UI state over arbitrary sleeps.

Use page.waitForTimeout() only while investigating. Fixed sleeps make tests slower and still fail when a system needs longer than the chosen delay.

10. Common errors and fixes

Error pattern Likely cause Fix
“Test timeout of 30000ms exceeded” Test body or setup exceeded the test budget. Profile setup; raise timeout or test.setTimeout() only for the slow test.
“expect(…).toBeVisible” timed out Assertion budget is still 5 seconds, or locator/state is wrong. Check the locator and state; use assertion { timeout: ... } or expect.timeout.
“locator.click” timed out Element is hidden, covered, disabled, moving, or missing. Inspect the trace; fix UI state or locator, then set an action timeout for a genuinely slow control.
“page.goto” timed out Server, DNS, TLS, redirect, or resource load is slow. Check the URL from the test environment, set navigation timeout, and use an appropriate waitUntil.
Works locally, fails in CI Slower CPU, cold services, missing data, or environment differences. Capture a trace, verify dependencies and secrets, and increase only the affected scope.
Run never finishes Timeout disabled with 0, or a wait depends on perpetual network activity. Restore finite limits; avoid networkidle and assert a concrete UI condition.

11. Performance, reliability, and cost trade-offs

Large global limits reduce false failures but delay feedback when a selector is broken. Small limits fail fast but can punish a cold CI worker. A balanced policy is a finite project default, local increases for known slow paths, and a global run cap.

  • Performance: Assertions retry efficiently; replacing them with sleeps wastes the full sleep on fast runs.
  • Reliability: Flakes usually indicate an unstable wait condition, test data race, or service dependency. Raising every timeout hides that signal.
  • Cost: Longer waits consume CI minutes and browser capacity. Bound navigation and global run time, especially with parallel workers.
  • Observability: Keep traces or screenshots on failure so the next timeout is diagnosable instead of guessed.

12. Or skip the browser setup

If your goal is a clean image or PDF of a URL rather than an end-to-end interaction, ScreenshotNeo provides a single screenshot API request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the result with X-Page-Verdict and X-Billed headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

ScreenshotNeo removes common overlays before capture so the resulting image is clean.
ScreenshotNeo removes common overlays before capture so the resulting image is clean.

See the ScreenshotNeo API documentation for all options, including full-page and element capture, device presets, custom viewport and retina scale, PDF paper and margins, custom CSS/JavaScript, waits, request blocking, headers/cookies, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed links, async webhooks, bulk capture, usage, and the OpenAPI spec.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is on every plan. Create a free ScreenshotNeo account and keep your Playwright budget for tests that need real browser interaction.

13. FAQ

What is Playwright’s default timeout?

Playwright Test documents 30 seconds for a test and 5 seconds for an auto-retrying assertion. Action and navigation defaults in the test-runner options are no timeout unless you set them.

Does increasing timeout fix assertion failures?

No. The test timeout and assertion timeout are independent. Set expect.timeout or an assertion-level timeout, then verify the locator and expected state.

Should I use networkidle?

Playwright documents it as discouraged for tests. Use a concrete web assertion that represents readiness.

Can I disable a timeout?

For Page and BrowserContext timeout APIs, timeout: 0 disables the timeout. Avoid unbounded waits in normal test runs.

Which setting wins: page or context?

Page-level defaults take priority over context-level defaults. A page’s navigation timeout takes priority over its general timeout.