In Playwright, what changes when you set use.actionTimeout instead of leaving it at its default?
answer
- Default is no separate limit
- Set under use, not top level
- Turns a test timeout into an action error
- A per-call option beats the config
- Test clock is still the outer bound
basics
~20 sPlaywright 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.
solid answer
~40 s`actionTimeout` and `navigationTimeout` live under `use` in `playwright.config.ts` and both default to `0`, meaning no separate limit: a `locator.click()` that never becomes possible, or a `page.goto()` that never settles, simply burns the remaining test budget and the run reports `Test timeout of 30000ms exceeded`. Set `use: { actionTimeout: 10_000, navigationTimeout: 20_000 }` and each call gets its own clock — the stuck click fails as `locator.click: Timeout 10000ms exceeded` with its call log, and the test keeps enough budget for teardown and artefacts. An explicit option on the call wins over the config, so `page.goto(url, { timeout: 60_000 })` covers the one genuinely slow screen. The test timeout is still the outer bound: an action budget larger than it can never fire.
code
typescript · 10 linesimport { defineConfig } from '@playwright/test';
export default defineConfig({
timeout: 30_000,
use: {
baseURL: 'https://admin.internal.example',
actionTimeout: 10_000,
navigationTimeout: 20_000,
},
});go deeper
Know that these two keys live under use in the config and that both start at zero, meaning no separate limit. Actions and navigations get their own budgets only if you ask for them.
Explain what changes: which clock notices the stall, what the error text names, and how a per-call timeout option overrides the configured default for one call.
Argue the tradeoff on a real suite. A tight action budget yields named failures and preserves teardown time, at the price of per-call overrides wherever a control is honestly slow.
Treat the number as an interaction-latency policy for the product, not a test detail. Decide whether the suite asserts a responsiveness contract, and who owns the exceptions when it does.
## The default is "no separate limit" Under `use` in `playwright.config.ts`, `actionTimeout` and `navigationTimeout` both default to `0`, and zero here means *no clock of its own*: - `actionTimeout` bounds actions — `locator.click()`, `locator.fill()`, `locator.check()`, `locator.selectOption()` and their siblings. - `navigationTimeout` bounds navigations — `page.goto()`, `page.reload()`, `page.goBack()`, `page.waitForURL()`. - Neither bounds assertions; auto-retrying matchers spend `expect.timeout` instead. With both left at their defaults, a click on a control that never becomes clickable waits until the **test** clock expires. The run then reports `Test timeout of 30000ms exceeded` — accurate, but it describes the container rather than the call, and that one stuck action has consumed every second the rest of the test needed. ## What setting them changes ```typescript use: { actionTimeout: 10_000, navigationTimeout: 20_000, } ``` The same stuck click now fails on its own budget as `locator.click: Timeout 10000ms exceeded`, with the call log of what Playwright was waiting for. Three things follow: 1. The failure is attributed to a call rather than to the test as a whole, so triage starts at the right line. 2. The test still has budget left, so teardown, screenshots and trace finalisation complete instead of being cut short. 3. The number becomes a stated policy — "no interaction in this console should take more than ten seconds" — that the suite now enforces on every action. ## Precedence An explicit option on the call always wins over the configured default: - `locator.click({ timeout: 5_000 })` uses five seconds whatever `actionTimeout` says. - `page.goto('/admin/reports', { timeout: 60_000 })` covers the one screen that legitimately takes a minute. - `page.setDefaultTimeout(ms)` and `page.setDefaultNavigationTimeout(ms)` change the defaults for a given page at runtime, which is occasionally useful from inside a fixture. The test timeout remains the outer bound in every case. Setting `actionTimeout: 60_000` under a 30-second test timeout is inert: the test clock fires first and you are back to an exhausted test budget. ## The tradeoff | | `actionTimeout` unset | `actionTimeout` set | |---|---|---| | A stuck click reports | the test budget exhausted | that action, with its call log | | Remaining test budget | consumed by the stuck call | preserved for the rest | | A legitimately slow action | just works | needs a per-call override | | Suite-wide policy | implicit | explicit and enforced | The cost is real. One global number applies to every interaction, so the single genuinely slow control — a report export in an internal admin console that takes twenty seconds to enable its button — now needs `{ timeout: 30_000 }` written at that call. That is usually a feature: the override documents the slow spot at the slow spot, instead of burying it in a default that covers four hundred fast ones. ## Choosing a shape - Keep `actionTimeout` comfortably under the test timeout, so an action failure leaves room for teardown rather than colliding with the outer clock. - Let `navigationTimeout` be the larger of the two; a cold page load is honestly slower than a click on a rendered page. - Set both per project when one environment in the matrix is slower, rather than raising the numbers for everyone. - Leave them unset only when you have deliberately decided the test clock is the only budget you want to reason about — which is a defensible choice for a small suite, and a painful one for a large one. The through-line: these two keys do not make anything wait longer or shorter in the happy path. They change *which clock notices* when something never happens, and therefore what the report tells you.
- Does actionTimeout bound expect(locator).toBeVisible() as well?No. Auto-retrying assertions spend the expect budget, 5000 ms by default, while actionTimeout bounds actions such as click, fill and check. They are separate clocks under separate config keys, and raising one does nothing for the other.
- Why not leave actionTimeout unset and rely on the test timeout?Because the failure is then reported as an exhausted test budget rather than a bounded action failure, and the one stuck call consumes the time the rest of the test needed. An action budget makes the suite state a per-interaction expectation.
saying these in an interview costs you the question
- Thinks actionTimeout defaults to 30 seconds
- Sets actionTimeout above the test timeout and expects it to fire
- Believes actionTimeout also bounds expect assertions
- Uses actionTimeout to cover a slow page.goto navigation
- Raises actionTimeout globally to fix one slow screen