skip to content

In Playwright, why does page.clock.setSystemTime move the clock without firing any timers?

level: middleimportance: should knowfreq 33%

answer

  1. It answers a different question from the others
  2. Models an operating system clock correction
  3. Reported date moves, schedules do not
  4. Timers count ticks, Date reports wall time
  5. The only verb that can move time backwards

basics

~20 s

It 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 lines
typescript
import { 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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