Skip to content

[Bug]: page.clock does not control Temporal.Now #42830

Closed

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:

  1. npm init playwright@latest new-project (TypeScript, all three browsers)
  2. Save the test below as tests/temporal-clock.spec.ts
  3. 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

  1. ashrafiucse commented on Sep 21, 2026

    @ashrafiucse
    Contributor

    Happy to take this one — upstream @sinonjs/fake-timers added Temporal support in sinonjs/fake-timers#569 (faking the Temporal.Now.* surface), so I'd port that approach into the vendored clock.ts alongside the existing createIntl/createDate. Could this be assigned to me?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions