In Playwright, what is the difference between the test timeout and the expect timeout?
answer
- Two clocks, not one
- One bounds a test, one an assertion
- Thirty seconds versus five seconds
- The expect key, not the top level
- Inner clock spends the outer budget
basics
~10 sPlaywright 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 sTwo 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 linesimport { defineConfig } from '@playwright/test';
export default defineConfig({
timeout: 30_000,
expect: {
timeout: 5_000,
},
});go deeper
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.
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.
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.
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