What happens to a 30-second polling timer when a Playwright test calls page.clock.fastForward('05:00')?
answer
- One verb jumps, the other ticks through
- Think of a laptop lid closing and reopening
- Due timers fire at most once on a jump
- Counting interval fires needs the ticking verb
- Ticks take ms or mm:ss strings
basics
~20 sThe clock jumps five minutes ahead and the poll fires once, not ten times. fastForward models closing a laptop lid: due timers fire at most once on landing. Use page.clock.runFor to fire every intermediate tick.
solid answer
~40 s`page.clock.fastForward('05:00')` jumps the page's clock five minutes forward and fires each due timer **at most once** — exactly what a browser does when a laptop lid is closed and reopened. A 30-second `setInterval` polling room availability therefore runs once, not ten times, and any counter the page increments per poll will read 1. `page.clock.runFor('05:00')` is the other verb: it ticks through the five minutes, firing every callback in order, so the same interval fires ten times and each intermediate render happens. Choose by what the assertion needs: `fastForward` when you only care about the state on the far side of the jump (a session expired, a hold released), `runFor` when the intermediate work matters. Both accept milliseconds or `'mm:ss'` / `'hh:mm:ss'` strings.
code
typescript · 11 linesimport { expect, test } from '@playwright/test';
test('guest is signed out after five idle minutes', async ({ page }) => {
await page.clock.install();
await page.goto('/guest/profile');
await page.getByRole('button', { name: 'Edit guest details' }).click();
// Jump: each due timer fires once, as if the lid had been shut.
await page.clock.fastForward('05:00');
await expect(page.getByText('Signed out due to inactivity')).toBeVisible();
});go deeper
Learn the two verbs by name: fastForward jumps and fires each due timer once, runFor ticks through and fires them all. Both take milliseconds or an mm:ss string.
Explain the mechanics with a number: a 30-second interval over a five-minute jump fires once under fastForward and ten times under runFor, and say which your assertion depends on.
Show that you pick the verb from the assertion, not habit, and that you know runFor over a long window with a fast interval executes every callback for real and slows the suite down.
Frame it as a contract the suite states out loud: which specs are allowed to drive time at all, which verb each uses, and how a reviewer tells a wrong-verb failure from a genuine product regression.
Once `page.clock.install()` has replaced the page's timer functions, a Playwright test owns the passage of time and has to say *how* time should move. The two advance verbs are `runFor` and `fastForward`, and they differ in what happens to the timers in between. ## fastForward: the laptop lid `page.clock.fastForward(ticks)` jumps the clock forward by `ticks` and fires each due timer **at most once**. The mental model in the Playwright docs is a user closing the laptop lid and reopening it later: while the machine slept, no callbacks ran; on waking, the browser notices each overdue timer and runs it once, not once per missed period. For a room-availability poll registered as `setInterval(refresh, 30_000)` on a hotel search page: - `fastForward('05:00')` fires `refresh` **once**, and the clock reads five minutes later. - Any per-poll counter the page keeps reads `1`. - Any state that only the tenth poll would have produced never exists. ## runFor: ticking through `page.clock.runFor(ticks)` advances the clock through the interval, firing all the time-related callbacks in order as it goes. The same 30-second interval under `runFor('05:00')` fires **ten** times, each render happens, and timers scheduled *by* those callbacks are themselves fired if they come due inside the window. It is the fine-grained option, and the one you want when the intermediate steps are the behaviour under test — a progress bar, a retry-with-backoff, an animation stepping through frames. ## Choosing between them | Need | Verb | Why | |---|---|---| | Assert the state after a long idle gap | `fastForward` | Cheap, models a real sleep/wake | | Count how many times a poll ran | `runFor` | Every intermediate tick fires | | Reach an expiry deadline and stop | `pauseAt` | Jumps like `fastForward`, then holds time | | Let real time flow again afterwards | `resume` | Timers go back to firing on their own | `pauseAt(time)` is worth naming here because it is `fastForward` with a brake: it jumps to an absolute moment, fires due timers at most once, and then stops the clock so nothing fires until the test calls `runFor`, `fastForward`, `pauseAt` again or `resume`. ## The ticks argument Both verbs take either a number of milliseconds or a human-readable string: 1. `'08'` — eight seconds. 2. `'01:00'` — one minute. 3. `'02:34:10'` — two hours, 34 minutes and ten seconds. Anything else is rejected; the clock only understands numbers, `'mm:ss'` and `'hh:mm:ss'`, so `'5m'`, `'300s'` and `'PT5M'` all throw. Each segment must also be under 60. Direction matters too: time only moves forward. A negative `runFor` is a `TypeError`, and `fastForward` past a moment already gone fails with "Cannot fast-forward to the past" — to test a backwards shift you want `setSystemTime`, not an advance verb. ## Why the distinction bites in review The failure mode is silent rather than loud. A test that jumps five minutes and asserts "the availability list refreshed" passes under either verb, because one refresh is enough to satisfy it. The same test asserting "the list refreshed ten times", or depending on state accumulated across polls, passes only under `runFor` — and a reviewer who assumes `fastForward` replays every missed tick will read the failure as a product bug. The reverse mistake costs time rather than correctness: `runFor` on a long window with a fast interval genuinely executes every callback. Ticking a 100 ms animation frame through two simulated hours is tens of thousands of real callback invocations, and the test slows down accordingly. When the assertion only concerns the far side of the gap, `fastForward` gets there in one step. ## Practical shape of a test Install before navigating, let the page load on the fake clock, act, then advance: - `install({ time })` — start slightly before the moment under test. - `page.goto(...)` and interact as normal. - `fastForward` or `runFor` to move time. - Assert with a web-first matcher, which retries while the page settles after the jump.
- What does page.clock.fastForward do if the target moment is already in the past?It throws — the clock refuses to fast-forward to the past, and `runFor` rejects a negative tick count with a TypeError. Advancing is strictly forward. To make the page read an earlier date, use `setSystemTime`, which shifts the reported wall time without firing or rescheduling anything.
- Which tick strings does Playwright's clock accept?Numbers of milliseconds, or `'ss'`, `'mm:ss'` and `'hh:mm:ss'` — so `'08'` is eight seconds, `'01:00'` a minute and `'02:34:10'` two hours 34 minutes ten seconds. Segments must be under 60. Formats like `'5m'` or `'300s'` throw rather than being parsed.
- After a long fastForward, why still assert with a retrying matcher?The jump fires the due callbacks, but the resulting renders, fetches and state updates settle asynchronously. A retrying web-first assertion absorbs that settle time, while a one-shot read of text or a count can sample the DOM a frame too early.
fastForward is waking a sleeping laptop — the alarms that came due while it slept ring once between you and the screen. runFor is holding the minute hand and walking it round, so every alarm on the dial rings as you pass it.
saying these in an interview costs you the question
- Thinks fastForward replays an interval once per missed period
- Expects runFor to skip the intermediate timer callbacks
- Passes tick strings like 5m or 300s
- Believes fastForward can move the clock backwards
- Confuses advancing the fake clock with waiting out real time
- Uses runFor across simulated hours of animation frames