Repository navigation
Description
Filed by Claude Fable 5.1 on behalf of Sérgio Carneiro (@sergiocarneiro)
Version
1.63.0
Steps to reproduce
The reproduction is a single test against about:blank, with no dependencies beyond Playwright:
npm init playwright@latest new-project(TypeScript, all three browsers)- Save the test below as
tests/temporal-clock.spec.ts npx playwright test temporal-clock
import { test, expect } from "@playwright/test";
test("Temporal.Now follows page.clock", async ({ page }) => {
await page.clock.setFixedTime(new Date("2020-01-01T00:00:00Z"));
await page.goto("about:blank");
const [date, temporal] = await page.evaluate(() => [
new Date().toISOString(),
Temporal.Now.instant().toString(),
]);
expect(date).toBe("2020-01-01T00:00:00.000Z");
expect(temporal).toBe("2020-01-01T00:00:00Z");
});Expected behavior
Both assertions pass: once page.clock controls time, Temporal.Now reports the same instant as Date.
That covers every clock-reading method of Temporal.Now — instant(), zonedDateTimeISO(), plainDateTimeISO(), plainDateISO() and plainTimeISO() — after clock.install(), setFixedTime() or setSystemTime(), with timeZoneId() and the rest of the Temporal namespace untouched.
Actual behavior
The Date assertion passes and the Temporal one fails in Chromium, Firefox and WebKit — Temporal.Now keeps reading the host clock:
Error: expect(received).toBe(expected) // Object.is equality
Expected: "2020-01-01T00:00:00Z"
Received: "2026-09-21T11:07:13.4149Z"
11 |
12 | expect(date).toBe("2020-01-01T00:00:00.000Z");
> 13 | expect(temporal).toBe("2020-01-01T00:00:00Z");
| ^
Additional context
page.clock fakes Date, the timers, performance and Intl.DateTimeFormat, but not Temporal. Temporal.Now is the only part of Temporal that reads the clock, by design, so that is the whole surface. @sinonjs/fake-timers, which clock.ts derives from, added exactly this in 15.4.0 (sinonjs/fake-timers#569), in the same shape as the existing createIntl.
Temporal is Stage 4 and ships natively in all three engines Playwright bundles, and lint rules such as unicorn/prefer-temporal now steer code from new Date() to Temporal.Now. That migration silently breaks time-dependent tests: nothing throws, the page just evaluates against real time, so the failure reads as a bug in the app's own scheduling logic rather than in how the clock is read.
A Temporal polyfill masks it, because polyfills read Date.now(). The same suite passes until the browser gains native support, then fails with no code change.
Environment
System:
OS: macOS 26.6.2
CPU: (8) arm64 Apple M3
Memory: 309.09 MB / 16.00 GB
Binaries:
Node: 25.9.0 - /Users/sergiocarneiro/Library/pnpm/bin/node
npm: 11.15.0 - /Users/sergiocarneiro/Library/pnpm/bin/npm
pnpm: 11.12.0 - /Users/sergiocarneiro/Library/pnpm/bin/pnpm
IDEs:
VSCode: 1.138.0 - /usr/local/bin/code
Claude Code: 2.1.278 - /Users/sergiocarneiro/.local/bin/claude
Languages:
Bash: 3.2.57 - /bin/bash
npmPackages:
@playwright/test: 1.63.0 => 1.63.0
Activity
ashrafiucse commentedon Sep 21, 2026
Happy to take this one — upstream
@sinonjs/fake-timersadded Temporal support in sinonjs/fake-timers#569 (faking theTemporal.Now.*surface), so I'd port that approach into the vendoredclock.tsalongside the existingcreateIntl/createDate. Could this be assigned to me?