skip to content

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

level: principalimportance: should knowfreq 33%

answer

  1. One clock over the whole run
  2. Defaults to no limit at all
  3. Per-test budgets never add up
  4. Truncates the run rather than attributing
  5. Sized above the honest worst case

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.

solid answer

~50 s

`globalTimeout` is a top-level key in `playwright.config.ts`, defaults to `0` (no limit), and bounds the wall-clock time of the whole run rather than any one test. Per-test budgets do not compose into a run budget: nine hundred tests that each stay inside 30 seconds still add up, retries spend those budgets again, and the time between tests is charged to nobody. Setting `globalTimeout: 45 * 60 * 1000` converts an open-ended stall into a bounded failure the runner itself reports, instead of the CI agent eventually killing an opaque job. The cost is attribution: when it fires the run is truncated, so tests that never started produce no result and you learn that the budget blew rather than which test was slow. Size it above the honest worst case and treat it firing as a capacity signal.

code

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

export default defineConfig({
  timeout: 30_000,
  globalTimeout: 45 * 60 * 1000,
  workers: 4,
});

go deeper

for a junior

Know that it exists and that it defaults to no limit: nothing stops a Playwright run taking as long as it takes unless globalTimeout is set in the config.

for a middle

Explain that it bounds the whole run rather than any one test, and that per-test budgets never sum into a run budget once workers, retries and startup cost are counted.

for a senior

Derive a number from the suite: worst-case duration times count divided by workers, plus retries and setup, plus headroom — and recognise a fired budget as a truncated run with no per-test attribution.

for a principal

Own the policy across the estate: which clock is allowed to be the one that fails, how shards and the CI job limit interact with it, and what the team does when the budget blows instead of raising it by reflex.

## What globalTimeout actually bounds `globalTimeout` is a top-level key in `playwright.config.ts`. It defaults to `0` — no limit — and bounds the **wall-clock duration of the whole run**: every worker, every project, and all the time that is not inside any individual test. When it expires the runner stops the run and reports a run-level timeout naming the budget, rather than a failure attached to a test. Nothing else in the config does this. `timeout` bounds one test, `expect.timeout` one assertion, `actionTimeout` one action — and none of them compose upwards. ## Why per-test budgets never add up to a run budget Take an internal admin console suite of 900 tests on 4 workers with a 30-second per-test budget: - the arithmetic ceiling is 900 × 30 s ÷ 4 ≈ 1 h 52 m, and every one of those tests is "within budget"; - retries multiply the worst case, because a retried test spends a full budget again; - fixture and hook time is charged to tests, but worker startup, browser launch and queueing between tests are charged to nobody; - a degraded environment slows everything a little, which appears nowhere as a per-test failure until something crosses 30 seconds. A run can therefore stall for hours without a single test misbehaving by its own clock. `globalTimeout` is the only budget that notices that. ## What it costs when it fires This is the part that decides how you use it: 1. The run is **truncated**. Tests that had not started produce no result of their own — you are missing data, not reading it. 2. There is no attribution. The error says the budget blew, not which test or fixture consumed it, so you go to the durations of what did finish, the reporter output, or the traces. 3. It is indistinguishable from a legitimately slower run: a cold image cache, a larger matrix, a shared environment under load. So a fired `globalTimeout` is a **capacity signal, not a defect report**. The response is to look at where the time went — not to reflexively raise the number, and certainly not to lower it in the hope that the suite gets faster. ## Sizing it 1. Start from the honest worst case: worst-case test duration × test count ÷ workers. 2. Add the multiplier for the retry policy you actually run. 3. Add setup, teardown and per-worker startup cost. 4. Add headroom for a bad day, so ordinary variance is not reported as a hang. 5. Round up, and revisit whenever the suite size or the worker count changes. A budget set just above the median run turns normal variance into red builds, and a team that sees the run-level red often enough stops reading it. ## How it sits next to the other run-level clocks | Clock | Scope | Who reports the failure | |---|---|---| | `timeout` | one test | Playwright, against that test | | `globalTimeout` | the whole run | Playwright, as a run-level error | | CI job limit | the whole job | the CI agent, by killing the process | The agent's own limit will eventually stop a stalled job, but it does so from outside: the process is killed and you are left with a red job rather than a run that ended on its own terms. A `globalTimeout` set somewhat below the job limit keeps the ending inside Playwright. ## Two mechanical notes worth knowing - The budget applies to *a run*, and each shard of a sharded suite is its own run in its own process — so a shard carries the whole `globalTimeout`, not a fraction of it. Splitting a suite across four jobs without revisiting the number quadruples the total time the estate will tolerate. - It is a wall-clock budget, not a CPU one. Adding workers reduces the elapsed time a given suite needs but does nothing to the budget itself, so worker-count changes and `globalTimeout` should move together rather than drift apart.

  • What happens to tests that have not started when globalTimeout fires?
    They do not run and produce no result of their own; the run ends with a run-level timeout error. That is why a fired global budget reads as missing data and a capacity problem rather than as a set of test failures you can triage.
  • Why not simply rely on the CI job's own time limit?
    Because the agent stops the job from outside, killing the process and leaving an opaque red. globalTimeout ends the run inside Playwright, so the failure is reported as a run-level timeout against a budget you deliberately chose.

saying these in an interview costs you the question

  • Thinks globalTimeout is the per-test default
  • Believes globalTimeout applies to each worker separately
  • Sets globalTimeout tight to force the suite to run faster
  • Assumes a globalTimeout failure names the slow test
  • Relies only on the CI job limit and skips globalTimeout