Why would a Playwright test call page.clock.install instead of page.clock.setFixedTime?
answer
- One pins the date, one owns time
- Does the page render or compute with time?
- Timers keep running under a frozen date
- install fakes Date, timers and animation frames
- Only install unlocks runFor and pauseAt
basics
~10 spage.clock.setFixedTime only pins what Date.now and new Date report; timers keep running on real time. page.clock.install replaces the whole timer stack, so the test can pause, tick or jump the page's clock.
solid answer
~40 sBoth live on `page.clock`, but they control different things. `setFixedTime(time)` makes `Date.now()` and `new Date()` return one fixed value while `setTimeout`, `setInterval` and `requestAnimationFrame` keep firing on real time — perfect when the page merely *renders* a date. `install({ time })` swaps out the page's entire time stack (`Date`, the timer functions, animation frames, idle callbacks and `performance`), which is what lets a test then call `runFor`, `fastForward`, `pauseAt` and `resume`. So you reach for `install` as soon as the page *computes* with time: a hotel checkout that counts down a ten-minute room hold, a relative "booked 3 minutes ago" label, or a poll you want to advance past. `page.clock` shipped in Playwright 1.45 and behaves this way in 1.63.
code
typescript · 11 linesimport { expect, test } from '@playwright/test';
test('held-room countdown ticks down', async ({ page }) => {
// A fixed date alone would freeze this countdown at its starting value.
await page.clock.install({ time: new Date('2026-03-04T09:00:00') });
await page.goto('/rooms/deluxe-king');
await page.getByRole('button', { name: 'Hold this room' }).click();
await page.clock.runFor('01:00');
await expect(page.getByTestId('hold-remaining')).toHaveText('9:00');
});go deeper
Remember the split: setFixedTime pins what Date reports, install replaces the timers too. If the feature counts down or refreshes on an interval, you need install.
Be able to name what install fakes — Date, setTimeout, setInterval, animation frames, idle callbacks and performance — and explain why a frozen Date.now with live timers leaves a countdown stuck.
Show the ordering judgment: install before navigation, start slightly before the moment under test, let the page load, then pause. Explain why a page booted on a frozen clock can hang on a bootstrap timer.
Own the blast radius. The clock is context-wide and survives navigation, so a fixture that installs it for every test changes the timing contract of the whole suite; scope it to the specs that need it.
Playwright gives a test control over the page's sense of time through `page.clock`. It is the same object as `context.clock`: the fake clock is installed for the whole browser context, so every page and every iframe inside that context shares one clock. Two entry points look interchangeable and are not. ## What each call actually replaces `page.clock.setFixedTime(time)` pins what the page **reads**. `Date.now()` and `new Date()` return the value you passed, on every call, and nothing else changes. `setTimeout`, `setInterval`, `requestAnimationFrame` and `performance` all keep running against real wall time, at real speed. You can call it again later to move the reported date to a new value. `page.clock.install(options)` replaces the page's whole time stack with one the test drives. The globals it fakes are: - `Date` - `setTimeout` and `clearTimeout` - `setInterval` and `clearInterval` - `requestAnimationFrame` and `cancelAnimationFrame` - `requestIdleCallback` and `cancelIdleCallback` - `performance`, including `Event.timeStamp` Only after `install` do the advance verbs mean anything: `runFor(ticks)` ticks through time firing every callback on the way, `fastForward(ticks)` jumps ahead firing each due timer at most once, `pauseAt(time)` jumps to a moment and stops time dead, and `resume()` lets it flow again. `install` takes an optional `time` (a number of epoch milliseconds, a string, or a `Date`) and defaults to the current system time. ## The deciding question: does the page render the date or compute with it? A room-detail page that prints `new Date().toLocaleDateString()` into a "check-in" field only *renders* time. `setFixedTime` is enough, it is one line, and it is what the Playwright docs recommend starting with. A checkout page that holds a room for ten minutes computes with time. Its code looks like `const remaining = Math.round((endTime - Date.now()) / 1000)`, re-rendered by a one-second interval. Under `setFixedTime` the interval still fires every real second, but `Date.now()` never moves, so the banner is frozen on "held for 10:00" forever and the expiry branch is unreachable. The page is not broken — the test froze half of time and left the other half running. `install` is the fix, because it moves the timers and the date together. ## Side by side | Behaviour | `setFixedTime` | `install` | |---|---|---| | `Date.now()` advances on its own | no | yes, until paused | | `setTimeout` / `setInterval` still fire | yes, on real time | yes, on the fake clock | | Test can jump ahead | no | yes, via `fastForward` or `pauseAt` | | Test can tick callback by callback | no | yes, via `runFor` | | Test can stop timers entirely | no | yes, via `pauseAt` | | Setup cost | one call, any time | must precede the page's own timers | ## Ordering, scope and lifetime The fake clock is delivered as an init script, so it applies to the document the page loads next and survives navigation inside the context. Two consequences matter in practice: 1. Call `install` **before** `page.goto`, so the page boots on the fake clock rather than registering timers against the real one. The documented rule is that if you use `install` at all, it must come before any other clock-related call. 2. Prefer starting the clock slightly *before* the moment under test and letting the page load naturally, then pausing. Loading a page with time already frozen can leave a spinner or a bootstrap timer that never resolves. Because the clock belongs to the context, you do not install it per page; a second tab opened in the same context is already on the same clock. ## Choosing in practice 1. Start with `setFixedTime` — the cheapest thing that could work. 2. If a countdown, a relative timestamp or a poll stalls, switch to `install({ time })` before navigation. 3. Then pick the advance verb by intent: `runFor` when every intermediate tick must fire, `fastForward` when you only care about the state on the far side of the jump, `pauseAt` when the assertion needs the clock held still. The cost of `install` is that the page is now entirely on your clock: anything the app expects to happen "eventually" happens only when the test says so. That is the point, and it is also why an over-installed clock can make a test hang while it waits for a timer nobody advanced.
- Can you still call page.clock.setFixedTime after page.clock.install?Yes. `setFixedTime` pins `Date.now()` and `new Date()` to a value while the installed timers keep running, so you can install for timer control and then freeze the visible date. Calling it again moves the pinned value; the timers are unaffected either way.
- Which pages does page.clock.install affect in a Playwright test?All of them in that browser context. `page.clock` and `context.clock` are the same clock, so every page and every iframe in the context runs on it, and it is delivered as an init script, so it applies across navigations too. You do not install it per page.
setFixedTime is painting a fixed time on the wall clock while the kitchen timers keep ticking; install replaces every timepiece in the building with ones you hold the winder for.
saying these in an interview costs you the question
- Thinks setFixedTime also pauses setTimeout and setInterval callbacks
- Calls install after the page has already scheduled its timers
- Believes a frozen Date.now still lets a countdown advance
- Reaches for install when the page only renders a static date
- Assumes the fake clock covers one page but not its iframes