skip to content

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

level: middleimportance: must knowfreq 66%

answer

  1. Widen one test, not the suite
  2. One call inside the test body
  3. A shorthand that triples the default
  4. Relative arithmetic inside beforeEach
  5. Zero means no limit at all

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.

solid answer

~40 s

Widen that test alone, never the suite. `test.setTimeout(120_000)` as the first statement in the test body replaces the configured `timeout` for that test only, and `test.setTimeout(0)` removes the limit entirely. `test.slow()` is the shorthand: it marks the test slow and triples its base budget, and it takes a conditional form such as `test.slow(process.env.CI === 'true', 'staff seeding is slower on CI')`. Inside `beforeEach`, `test.setTimeout(test.info().timeout + 30_000)` extends every test running that hook relative to whatever the config says, which survives a later config change better than a hardcoded number. The same call exists as `test.info().setTimeout(ms)`, usable from a fixture. Called inside `beforeAll`, it sets that hook's own budget rather than the tests'.

code

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

test('bulk-import 5000 staff accounts', async ({ page }) => {
  test.setTimeout(120_000);
  await page.goto('/admin/staff/import');
  await page.getByRole('button', { name: 'Start import' }).click();
  await expect(page.getByText('Import complete')).toBeVisible({ timeout: 90_000 });
});

go deeper

for a junior

Learn the two calls and where they go: test.setTimeout(ms) and test.slow() at the top of the test body. Both change only the test you are in, not the config.

for a middle

Explain that setTimeout replaces rather than adds, that slow triples the base budget, and that reading test.info().timeout lets a beforeEach extend every test relative to the configured value.

for a senior

Show judgment about where the widening lives. Keep the suite default tight so hangs fail fast, and make each extension visible at the test that needs it rather than in a config nobody revisits.

for a principal

Frame it as a policy question: what the default budget asserts about the suite, who may raise it, and how the team notices when widenings accumulate into a suite that no longer fails fast.

The configured `timeout` in `playwright.config.ts` is a default, not a law. Playwright offers three ways to change it for a single test, and they share one purpose: keep the suite-wide budget tight so a hang still fails fast, and spend extra time only where the work honestly needs it. ## test.setTimeout `test.setTimeout(ms)` sets the budget of the currently running test. Call it as the first statement in the body: ```typescript test('bulk-import staff accounts', async ({ page }) => { test.setTimeout(120_000); // ... }); ``` - It **replaces** the configured value for this test; it does not add to it. - `test.setTimeout(0)` removes the limit entirely. - The value in force is readable as `test.info().timeout`, which is what makes relative arithmetic possible. ## test.slow `test.slow()` marks the test as slow and **triples** its base timeout — 30 seconds becomes 90. It carries intent that a raw number does not: "this test is legitimately long", rather than "someone once picked 120000". It also has a conditional form that triples the budget only when the condition holds: ```typescript test.slow(process.env.CI === 'true', 'staff seeding is slower on the shared CI database'); ``` Put it in the test body or in a `beforeEach`. For a `beforeAll`, reach for `test.setTimeout()` instead — there it sets that hook's own budget. ## Widening from a hook A `beforeEach` runs inside the test's budget, so the hook can adjust it for every test in the file: ```typescript test.beforeEach(async () => { test.setTimeout(test.info().timeout + 30_000); }); ``` Reading `test.info().timeout` and adding to it beats hardcoding a number: lower the project's `timeout` later and the hook still contributes its 30 seconds instead of silently overriding the new, tighter budget. The same call exists as `test.info().setTimeout(ms)` — identical behaviour, reachable anywhere `testInfo` is, including inside a fixture. ## Which one to reach for | Situation | Reach for | |---|---| | One test does genuinely long work | `test.setTimeout(ms)` in the body | | The test is long for a reason worth naming | `test.slow()` | | Long only in one environment | `test.slow(condition, description)` | | Every test in a file needs headroom | `test.setTimeout(test.info().timeout + n)` in `beforeEach` | | Every test in a project is slower | `timeout` on that project in the config | ## Why not simply raise the global timeout 1. Every hang in the suite then burns the new, larger budget before reporting — a fourfold raise makes every failing run four times slower to tell you anything. 2. The slowness stops being visible. One test that needs 120 seconds is information; a suite where everything may take 120 seconds hides it. 3. Nobody can later tell which tests actually needed the room, so the number never comes back down. A per-test widening leaves a marker at exactly the place that needs explaining, and `test.slow()` leaves a named one. Both keep the default honest: a test that never asked for more time and still exceeds 30 seconds is a signal, not background noise. ## An admin console example Opening a staff record and toggling a permission is a five-second test. The nightly bulk import of five thousand accounts through the same console is not — it is a two-minute test that is *supposed* to take two minutes. Marking that one test with `test.setTimeout(120_000)` while the other four hundred stay on 30 seconds is the entire pattern, and the diff shows a reviewer exactly which test bought the extra time.

  • What does test.setTimeout(0) do, and when is it defensible?
    It removes that test's timeout entirely, so it runs until the run-wide budget or the CI agent stops it. Defensible for a deliberate local investigation; in CI it converts a hang into a stalled pipeline with no per-test failure to point at.
  • Why prefer test.info().timeout + 30_000 over a hardcoded number in beforeEach?
    It extends whatever the config currently says instead of pinning an absolute value. Tighten the project timeout later and the hook still adds its 30 seconds, rather than quietly reinstating the old, looser budget.

saying these in an interview costs you the question

  • Raises the global timeout to fix one slow test
  • Thinks test.slow() skips or reorders the test
  • Believes test.setTimeout adds to the existing budget
  • Calls test.slow() in beforeAll and expects tests to widen
  • Uses test.setTimeout(0) in CI and lets hangs run forever