Some scenarios depend on time, and real time makes them either slow or non-deterministic. A session expires in thirty minutes, a banner appears in a day, polling refreshes data every ten seconds, a discount is valid until a certain date. Waiting for this for real is impossible, and guessing with pauses means returning to the flakiness from the waiting chapter. Playwright solves this differently: page.clock lets the test control the page's time directly.
The mechanism replaces the page's clock and timers with controlled ones. clock.install sets this replacement - and it is done before navigation, before page.goto, so the application sees controlled time from the very start rather than real time. From that point Date, setTimeout, setInterval inside the page obey the test: time stops flowing on its own and moves only when and by as much as the scenario says.
For scenarios depending on a specific date there is fixing the time. setFixedTime makes the page consider that it is exactly this moment - and this makes reproducible the tests about "valid until December 31", "show a greeting on New Year's night", "expired yesterday". Instead of depending on when the run was launched, the test sets the date itself, and its result is the same on any day and in any CI time zone.
For timers and intervals there is fast-forwarding time. fastForward instantly winds the clock forward by a given interval - and what should have happened in thirty minutes (a session expiring) or in ten seconds (a polling tick) happens at once, without real waiting. runFor winds time in steps, letting intermediate timers fire. So the test checks time-based behavior in milliseconds rather than minutes.
It helps to see both modes together once - fixing the date and fast-forwarding a timer. Below is setting the clock before navigation, a fixed date for the scenario and a thirty-minute fast-forward checking session expiry. You return to this form when a time dependence surfaces in a scenario: a session, an expiry, polling, a delayed banner - everything people used to try to catch with real waiting.
The direct benefit is closing the "Time/TZ" row from the flaky chapter. Tests that failed at night, at the end of the month or in CI with a different time zone are fixed not by tuning the environment but by fixing time: the scenario sets the moment itself and does not depend on when and where it was run. Time non-determinism turns from a source of random failures into a controlled test input, just like data.
It is important to set the clock before the application reads it. If clock.install is called after page.goto, the application will manage to get the real time at startup, and the replacement will pick up only what happens later - while part of the logic has already run on the real clock. So time control is configured at the start of the scenario, before navigation, so the page lives in controlled time from the first frame rather than switching to it halfway.
The typical time-handling failures are predictable. Waiting real minutes for a session to expire or polling to fire - a slow and still unstable test. Depending on the system date instead of setFixedTime - failures at the end of the month and in a foreign time zone. And calling install too late, after navigation - part of the logic on the real clock and an unclosed race. Set the clock before goto, fix the date for date-dependent scenarios and fast-forward timers instead of real waiting.
// The clock is set BEFORE navigation, so the app lives in controlled time at once
await page.clock.install({ time: new Date('2026-07-26T10:00:00') })
await page.goto('/dashboard')
// A fixed date - a reproducible scenario about an expiry
await page.clock.setFixedTime(new Date('2026-12-31T23:59:00'))
// Fast-forward by 30 minutes: the session expires without real waiting
await page.clock.fastForward('30:00')
await expect(page.getByText('Session expired')).toBeVisible()