A Playwright test calls page.clock.pauseAt before page.goto and the room page never finishes loading — how should the clock calls be ordered?
answer
- The page needs time to be alive to boot
- Order the three calls before you debug the app
- Freeze after the load, never before it
- Start slightly before the moment under test
- Only runFor, fastForward, pauseAt or resume restart time
basics
~20 sInstall the clock before navigating, at a moment slightly before the time under test, let the page load with timers running, and only then pause. Pausing before navigation stops the bootstrap timers the page needs to finish loading.
solid answer
~40 sThe order should be `install` then `goto` then `pauseAt`. `page.clock.pauseAt` jumps the clock and then stops it: once time is frozen, **no** timer fires until the test calls `runFor`, `fastForward`, `pauseAt` again or `resume`. Freezing before navigation means the page boots into stopped time, so any bootstrap that depends on a `setTimeout`, a debounce, a retry or an animation frame never completes — the spinner spins forever and the test times out looking like a product bug. The documented pattern is to `install({ time })` at a moment slightly *before* the one under test, navigate, let the app initialise on a normally-flowing fake clock, and only then `pauseAt` the moment you want to assert against. `install` must also come before the page schedules its own timers, which is another reason it belongs before `goto`.
code
typescript · 12 linesimport { expect, test } from '@playwright/test';
test('checkout session expires at 10:00', async ({ page }) => {
// 1. Install before navigating, slightly before the moment under test.
await page.clock.install({ time: new Date('2026-03-04T09:58:00') });
// 2. Let the page boot with its timers running normally.
await page.goto('/checkout/BK-4821');
// 3. Only now jump to the deadline and hold time still.
await page.clock.pauseAt(new Date('2026-03-04T10:00:00'));
await expect(page.getByRole('alert')).toHaveText('Your booking session has expired');
});go deeper
Learn the order and keep it: install, navigate, then pause. A page whose clock is frozen before it loads can never finish loading, because its startup work is scheduled on timers.
Explain the mechanism: pauseAt jumps and then stops time, and after it nothing fires until runFor, fastForward, another pauseAt or resume is called. Say why an init script must precede the navigation.
Triage from the symptom. A spinner that never resolves, real dates on screen, or deadline behaviour during hydration each map to a specific ordering mistake, and you should reach for that map before opening the application code.
Make the ordering unmissable rather than remembered: a shared fixture or helper that installs before navigation and resumes on teardown removes a whole family of failures that otherwise get triaged as product bugs.
This failure is one of the most common ways a `page.clock` test goes wrong, and it reads like an application hang rather than a test defect. ## What pauseAt actually does `page.clock.pauseAt(time)` does two things in one call: it jumps the clock forward to the given moment, firing each due timer at most once on the way, and then it **stops time**. After it returns, nothing in the page moves on its own. No `setTimeout` resolves, no `setInterval` ticks, no `requestAnimationFrame` callback runs. Time only moves again when the test calls `runFor`, `fastForward`, `pauseAt` again, or `resume`. That is exactly the property you want when asserting against a deadline — and exactly the wrong property to hand a page that has not booted yet. ## Why pausing before navigation hangs the load A modern booking page does a surprising amount of its startup on timers: - Debounced hydration and deferred bundles behind a `setTimeout(..., 0)`. - Skeletons and spinners driven by `requestAnimationFrame`. - Retry-with-backoff on the first availability fetch. - Analytics and consent banners on an idle callback. - A one-second interval that renders the "held until" countdown. Under a paused clock none of that ever runs. The page is not slow and not broken; the test froze the mechanism the page uses to make progress. The symptom is a navigation or an assertion that times out with a spinner on screen, which sends people hunting through the app for a bug that does not exist. ## The order that works 1. `await page.clock.install({ time })` — before any navigation, with the start time set slightly *before* the moment under test. 2. `await page.goto(...)` — the page boots on a fake but normally-flowing clock, so bootstrap timers fire as usual. 3. Interact as the test needs: search, open a room, start a checkout. 4. `await page.clock.pauseAt(newDate)` — jump to the moment you want and hold it. 5. Assert. Optionally `runFor(...)` to tick further, or `resume()` to let time flow again. Step 1 matters for a second reason: the documented rule is that if you use `install` at all, it must come before any other clock-related call, because it replaces the page's native `Date`, `setTimeout`, `setInterval`, animation-frame and idle-callback functions. Registering an interval against the real functions and then installing leaves the page holding handles the fake clock knows nothing about, and clearing them afterwards is undefined behaviour. ## Choosing the starting time "Slightly before" is doing real work. Starting at 09:58 for a session that expires at 10:00 gives the page two simulated minutes of ordinary operation to load and settle, then a two-minute jump to the deadline. Starting *at* 10:00 risks the expiry firing during hydration, before the component that renders the banner has mounted — a race that looks like flake and is really a test-authoring bug. ## Symptom to cause | Symptom | Likely cause | |---|---| | Navigation or first assertion times out with a spinner | Clock paused before the page loaded | | Timestamps still show today's real date | `install` called after `goto`, so the page kept the native `Date` | | Deadline behaviour fires during load | Clock installed exactly at the deadline instead of before it | | Page frozen for the rest of the test | `pauseAt` used and `resume` never called | | Poll fires once, not per period | `fastForward` used where `runFor` was meant | ## Scope, and what to check next The clock belongs to the browser context, not the page: `page.clock` and `context.clock` are the same object, and it is delivered as an init script, so it survives navigation and applies to every page and iframe in that context. So the fix is never "install it again on the second page" — if a second surface shows real dates, the install happened after that page's document was created, not on the wrong object. When triaging this in a suite, the questions in order are: was `install` before `goto`; was the start time before the deadline; was the clock left paused into the next step; and is the verb after the pause the one the assertion needs. Four checks, and they cover almost every clock-shaped failure.
- The page loads fine but still renders today's real date. What is wrong?The clock was installed after the document existed. `page.clock.install` is applied as an init script, so it has to be in place before `page.goto` for the new document to boot on the fake `Date`. Move the install above the navigation and the rendered date follows.
- After page.clock.pauseAt, what makes the page's timers run again?Only a further clock call: `runFor` to tick through firing everything, `fastForward` to jump firing each due timer once, another `pauseAt`, or `resume` to hand time back to real flow. Nothing in the page or a retrying assertion will restart it by itself.
- Why start the clock before the deadline rather than exactly on it?Because the page needs simulated time to hydrate and mount. Starting on the deadline can fire the expiry during load, before the component that renders it exists, producing a race that reads as flake. A couple of simulated minutes of headroom removes it.
Pausing before navigation is stopping the escalator before anyone steps on and then wondering why nobody reaches the top floor.
saying these in an interview costs you the question
- Pauses the clock before the page has finished loading
- Installs the clock after page.goto and expects fake timestamps
- Thinks intervals keep ticking quietly while the clock is paused
- Blames the application when a paused-clock page never settles
- Installs the clock separately on every page in a context
- Raises the test timeout instead of resuming the clock