In Playwright, why does page.clock.setSystemTime move the clock without firing any timers?
answer
- It answers a different question from the others
- Models an operating system clock correction
- Reported date moves, schedules do not
- Timers count ticks, Date reports wall time
- The only verb that can move time backwards
basics
~20 sIt exists to test how a page reacts to a time shift, not to drive timers. setSystemTime changes what Date reports; pending setTimeout and setInterval callbacks keep their original schedules, so nothing fires and nothing is rescheduled.
solid answer
~40 s`page.clock.setSystemTime(time)` sets the page's current wall time and stops there — no timer is fired, and no pending timer is rescheduled. That is deliberate: it models the machine's clock being changed underneath a running page, such as a summer-to-winter switch or a laptop crossing time zones, and a real clock change does not replay the callbacks that would have run in the skipped interval. Because timers run on a monotonic tick count rather than on the reported date, a pending `setTimeout` set for 30 seconds still fires 30 simulated seconds later, even if the reported date moved hours. If you want callbacks to run, advance the clock with `runFor` or `fastForward`. If you want the date pinned so it never drifts, use `setFixedTime`. `setSystemTime` is the advanced, narrow tool of the three.
code
typescript · 11 linesimport { expect, test } from '@playwright/test';
test('stay date survives a daylight-saving shift', async ({ page }) => {
await page.clock.install({ time: new Date('2026-03-28T23:30:00') });
await page.goto('/rooms/deluxe-king');
// Move the wall clock across the change; this fires no timers on its own.
await page.clock.setSystemTime(new Date('2026-03-29T03:30:00'));
await page.clock.runFor(1000);
await expect(page.getByTestId('check-in-date')).toHaveText('29 Mar 2026');
});go deeper
Know it as the odd one out: it changes what the page reads from Date and fires nothing. If you need a callback to run, that is what runFor and fastForward are for.
Explain why nothing fires: timers are scheduled against a monotonic tick count while Date reports a separate wall time, and this call rewrites only the second of the two.
Demonstrate it on a real defect class — a clock correction or a daylight-saving shift under a live page — and show that you trigger a re-render before asserting rather than expecting the shift itself to repaint.
Decide how far the suite goes here. Clock-shift cases catch real bugs but are easy to write wrongly, so name which surfaces get them and keep the rest on the simpler pinned-date approach.
`page.clock` offers three ways to change what time the page thinks it is, and `setSystemTime` is the one people reach for by name and then misuse. It sets the current system time as the page sees it — and triggers nothing. ## What it does and does not touch - It **does** change what `Date.now()` and `new Date()` return from that moment on. - It **does** let time keep flowing from the new value: unlike `setFixedTime`, the date is not pinned, so it advances again as the clock runs. - It **does not** fire any timer, due or otherwise. - It **does not** reschedule pending `setTimeout` or `setInterval` callbacks. - It **does not** change the context's time zone or locale — the instant moves, the formatting rules do not. The reason the callbacks are untouched is that the installed clock keeps two separate notions: a monotonic tick count that timers are scheduled against, and a wall time that `Date` reports. `setSystemTime` rewrites the second and leaves the first alone. A `setTimeout(fn, 30_000)` registered just before the call still comes due 30 simulated seconds later, no matter how far the reported date jumped. ## Why that is the right behaviour It matches what a real machine does. When an operating system corrects its clock by NTP, or a user flies from Lisbon to New York and the laptop shifts, the page's pending timers are not replayed and not cancelled — they keep counting down while `Date` starts reporting something else. Any bug caused by an application assuming the two move together is exactly what this method is for. On a multi-tenant hotel-booking site, that is a real class of defect: 1. A room-detail page renders "check-in 29 Mar" from a stored UTC instant. Shift the system time across a daylight-saving boundary and check the rendered day does not slip. 2. A guest-profile page shows "last stay 3 hours ago", computed as `Date.now() - lastStayAt`. Shift the clock and check the label recomputes on the next render rather than showing a negative or absurd value. 3. A search page caches quotes with a "valid until" instant. Shift the clock past it and check the page invalidates rather than serving stale prices. In every case the interesting behaviour is what the app does with a suddenly different `Date.now()` — not whether a timer fired. ## Picking it out of the three | Call | Date advances by itself | Fires due timers | Typical use | |---|---|---|---| | `setFixedTime` | no, pinned | no | Render a known date deterministically | | `setSystemTime` | yes, from the new value | no | Model a clock jump under a live page | | `fastForward` / `runFor` | yes, driven by the test | yes | Reach a deadline or drive a poll | The Playwright docs put it plainly: `setFixedTime` first, `install` when that is not enough, and `setSystemTime` only for advanced cases. If a candidate reaches for `setSystemTime` to make an expiry banner appear, they have picked the one verb in the family that will never make it appear on its own. ## Using it well After shifting, give the page a reason to re-render before asserting. A shift alone updates no DOM; the next render, the next interval tick or an interaction does. Two workable shapes: - Shift, then `runFor(1000)` so the page's own one-second interval redraws with the new date. - Shift, then interact — click a tab or a filter — and assert on what re-renders. `setSystemTime` accepts the same argument shapes as its siblings: epoch milliseconds, a parseable date string, or a `Date`. It can move the reported time backwards, which the advance verbs cannot — `fastForward` refuses to travel to the past and `runFor` rejects a negative count — so it is also the tool for testing a clock correction that goes the other way. ## The trap to name in review A test that shifts the system time and then asserts an expiry banner is visible will fail, and the failure looks like a product bug. The page never expired because nothing fired: no timer ran, and the component that would have noticed the new time never re-rendered. The fix is not a longer timeout; it is the right verb — `fastForward` or `pauseAt` to reach the deadline with timers firing, and `setSystemTime` reserved for the question it actually answers.
- How does page.clock.setSystemTime differ from page.clock.setFixedTime?`setFixedTime` pins the reported date so it never moves again until you change it; `setSystemTime` sets a new starting point and lets time flow on from there. Neither fires a timer, but only `setFixedTime` guarantees a stable value across a long assertion.
- Can you move a Playwright page's clock backwards?Only with `setSystemTime`. `fastForward` refuses with a fast-forward-to-the-past error and `runFor` rejects negative ticks, because both are advance operations that fire callbacks. Shifting backwards changes what `Date` reports without unwinding or replaying anything already scheduled.
saying these in an interview costs you the question
- Thinks setSystemTime fires the timers that are already due
- Expects it to reschedule pending setTimeout callbacks
- Uses it to make an expiry banner appear
- Believes it changes the browser context time zone
- Assumes moving time backwards replays the interval between