skip to content

Timeout Budgets

Several independent clocks - one per test, one per assertion, optional ones per action and navigation, one for the whole run - and the distinct error each produces when it expires.

on this pageshow

explore

questions

5

In Playwright, what is the difference between the test timeout and the expect timeout?

level: juniorimportance: must knowfreq 80%

answer

  1. Two clocks, not one
  2. One bounds a test, one an assertion
  3. Thirty seconds versus five seconds
  4. The expect key, not the top level
  5. Inner clock spends the outer budget

basics

~10 s

Playwright runs two independent clocks: the test timeout caps a whole test at 30 seconds by default, including its fixtures and hooks, while the expect timeout caps a single auto-retrying assertion at 5 seconds.

solid answer

~40 s

Two independent clocks. `timeout` is a top-level key in `playwright.config.ts`, defaults to 30000 ms, and is the budget for one test as a whole — the test body plus every fixture, `beforeEach` and `afterEach` that runs for it. When it expires the runner reports `Test timeout of 30000ms exceeded` and the test is over. `expect.timeout` sits under the `expect` key, defaults to 5000 ms, and budgets one auto-retrying assertion such as `expect(locator).toBeVisible()`; expiring it throws an ordinary assertion failure naming the matcher, so you learn which condition never became true. The assertion budget is spent inside the test budget, so `expect(...).toBeVisible({ timeout: 60_000 })` under a 30-second test never gets there — the outer clock fires first.

code

typescript · 8 lines
typescript
import { defineConfig } from '@playwright/test';

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

go deeper

for a junior

Memorise the two defaults and where each is configured: timeout at the top level of playwright.config.ts, expect.timeout under the expect key. Naming 30 seconds and 5 seconds confidently is the whole bar here.

for a middle

Explain the nesting. An assertion budget is spent inside the test budget, so a per-call timeout larger than the test timeout never fires, and fixture plus beforeEach time is charged to the test clock.

for a senior

Read the failure text as a diagnosis. A test timeout naming a fixture points at setup cost, an assertion failure names the condition that never held, and calling both flaky is how real slowness stays hidden.

for a principal

Own the policy on which clock the team may raise and where. A tight test budget with targeted per-call overrides keeps slowness visible; a global raise buys quiet while making every hang slower to surface.

Playwright Test does not have "a timeout" — it runs several independent clocks, each guarding a different unit of work and each producing a different error. The first two anyone meets are the **test timeout** and the **expect timeout**, and confusing them is the most common source of "I raised the timeout and it still failed". ## The test timeout `timeout` is a top-level key in `playwright.config.ts` and defaults to **30000 ms**. It is the budget for one test *as a whole unit of work*, which is more than the test body: - the test function itself; - every `beforeEach` and `afterEach` hook that runs for that test; - the setup and teardown of every fixture the test asks for. When it expires the runner aborts whatever is in flight and reports `Test timeout of 30000ms exceeded`. If the clock ran out during fixture setup, the message says so — `Test timeout of 30000ms exceeded while setting up "adminSession"` — which is the fastest way to separate "my test is slow" from "my setup is slow". A timed-out test is finished; nothing catches it and carries on. ## The expect timeout `expect.timeout` lives under the `expect` key and defaults to **5000 ms**. It is the budget for **one auto-retrying assertion**: the web-first matchers that keep re-checking until the page agrees, such as `expect(locator).toBeVisible()`, `expect(locator).toHaveText()` or `expect(page).toHaveTitle()`. Expiring it throws an ordinary assertion failure that names the matcher and shows what it last observed, so you learn *which condition never became true*. Matchers that do not retry — `expect(rows).toBe(3)` on a plain value — evaluate once and ignore this budget entirely. There is nothing to wait for. ## How the two nest The clocks are independent in configuration but not in effect: an assertion's budget is spent *inside* the test's budget. 1. The test clock starts when the test's fixtures begin setting up. 2. Each auto-retrying assertion starts its own 5-second clock when it is awaited. 3. Whichever expires first produces the error you actually see. That is why `expect(locator).toBeVisible({ timeout: 60_000 })` under a 30-second test timeout never waits a full minute: at 30 seconds the outer clock fires and you get a test timeout, not an assertion failure. Widening an assertion always means checking that the enclosing test budget can pay for it. ## Where each one is set | Clock | Config key | Default | Per-call override | Error on expiry | |---|---|---|---|---| | Test | `timeout`, top level | 30000 ms | `test.setTimeout(ms)` | `Test timeout of 30000ms exceeded` | | Assertion | `expect.timeout` | 5000 ms | `expect(...).toBeVisible({ timeout })` | assertion failure naming the matcher | Both can also be set per project, so a slower browser or a slower environment in an internal admin console's matrix carries a different budget without changing the default for everyone else. ## Reading a failure - A **test timeout** means the unit of work as a whole ran out of room: a slow fixture, a slow hook, or an action with no budget of its own that quietly consumed everything. - An **assertion failure from a retrying matcher** means the page never reached the asserted state within 5 seconds; the text shows the last value seen, which usually separates "wrong expectation" from "genuinely slow". - The distinction matters because the fixes differ. The first is a budgeting or setup problem; the second is a page or expectation problem. ## Why the defaults sit where they do Thirty seconds is generous for a single test and tight enough that a hang surfaces inside one CI step. Five seconds is long enough for an admin console's staff list to settle after a save, and short enough that a genuinely missing element does not cost half a minute in every test that touches it. Raise either number deliberately and locally rather than globally: a suite-wide raise buys quiet at the price of every hang taking proportionally longer to report.

  • Does expect.timeout apply to expect(value).toBe(...) as well?
    No. Only auto-retrying matchers — the ones taking a locator, page or API response — poll until they pass or the budget expires. A plain value matcher such as expect(count).toBe(3) evaluates once and fails immediately, so no waiting budget applies.
  • Where is the time spent in beforeEach and fixture setup charged?
    To the test timeout. The 30-second budget covers the test body plus every hook and fixture that runs for that test, which is why one slow fixture makes every test using it time out rather than failing on its own clock.

saying these in an interview costs you the question

  • Thinks expect.timeout is the per-test limit
  • Says a slow assertion is reported as a test timeout
  • Sets a 60 second assertion timeout under a 30 second test
  • Assumes fixture and beforeEach time sits outside the test timeout
  • Applies expect.timeout to non-retrying matchers such as toBe
open as a page

In Playwright, how do you give one slow test a larger budget than the configured timeout?

level: middleimportance: must knowfreq 66%

basics

~10 s

Call test.setTimeout(120_000) inside the test to replace its budget, or test.slow() to triple the configured one. Both affect only the current test, leaving the suite-wide timeout tight enough that a hang still fails fast.

open as a page

In Playwright, what changes when you set use.actionTimeout instead of leaving it at its default?

level: middleimportance: should knowfreq 52%

basics

~20 s

Playwright leaves actionTimeout at 0, so a stuck click waits until the test clock expires. Setting it under use gives every action its own budget, so the failure names the call and the test keeps its remaining time.

open as a page

A Playwright fixture that seeds admin-console staff accounts takes 45 seconds and every test times out. What do you change?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Fixture setup is charged to the test timeout by default, so a 45-second fixture blows a 30-second budget. Give that fixture its own timeout in its options tuple instead of raising the suite-wide timeout for every test.

open as a page

In Playwright, what does a run-wide globalTimeout buy a CI pipeline that per-test timeouts cannot?

level: principalimportance: should knowfreq 33%

basics

~20 s

globalTimeout caps the wall-clock duration of an entire Playwright run and defaults to no limit. Per-test budgets bound one test each, so a large suite and its retries can still run for hours with no test misbehaving.

open as a page