Skip to content

Commit 19f64e9

Browse files
kblokclaude
andauthored
Make Temporal.Now follow page.clock (#76)
Port upstream microsoft/playwright@0facd9234 (#42838) into the bundled clock script and add the four Temporal.Now tests from page-clock.spec.ts. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent d9597a8 commit 19f64e9

2 files changed

Lines changed: 128 additions & 2 deletions

File tree

‎src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,100 @@ public async Task CheckDateNowIsAnInteger2()
727727
Assert.That(await _page.EvaluateAsync<long>("Date.now()").ConfigureAwait(false), Is.EqualTo(1001));
728728
}
729729

730+
[PlaywrightTest("page-clock.spec.ts", "should follow fixed time")]
731+
[Test]
732+
[Timeout(TestConstants.DefaultTestTimeout)]
733+
public async Task ShouldFollowFixedTime()
734+
{
735+
await SkipUnlessTemporalAsync().ConfigureAwait(false);
736+
await _page.Clock.SetFixedTimeAsync(new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)).ConfigureAwait(false);
737+
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
738+
Dictionary<string, string> result = await _page.EvaluateAsync<Dictionary<string, string>>(
739+
@"() => {
740+
const Temporal = globalThis.Temporal;
741+
return {
742+
instant: Temporal.Now.instant().toString(),
743+
zonedDateTime: Temporal.Now.zonedDateTimeISO('UTC').toString(),
744+
plainDateTime: Temporal.Now.plainDateTimeISO('UTC').toString(),
745+
plainDate: Temporal.Now.plainDateISO('UTC').toString(),
746+
plainTime: Temporal.Now.plainTimeISO('UTC').toString(),
747+
};
748+
}").ConfigureAwait(false);
749+
Assert.That(result, Is.EqualTo(new Dictionary<string, string>
750+
{
751+
["instant"] = "2020-01-01T00:00:00Z",
752+
["zonedDateTime"] = "2020-01-01T00:00:00+00:00[UTC]",
753+
["plainDateTime"] = "2020-01-01T00:00:00",
754+
["plainDate"] = "2020-01-01",
755+
["plainTime"] = "00:00:00",
756+
}));
757+
}
758+
759+
[PlaywrightTest("page-clock.spec.ts", "should advance with the clock")]
760+
[Test]
761+
[Timeout(TestConstants.DefaultTestTimeout)]
762+
public async Task ShouldAdvanceWithTheClock()
763+
{
764+
await SkipUnlessTemporalAsync().ConfigureAwait(false);
765+
await _page.Clock.InstallAsync(0).ConfigureAwait(false);
766+
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
767+
await _page.Clock.PauseAtAsync(1000).ConfigureAwait(false);
768+
await _page.Clock.RunForAsync(2000).ConfigureAwait(false);
769+
Assert.That(await _page.EvaluateAsync<long>("() => globalThis.Temporal.Now.instant().epochMilliseconds").ConfigureAwait(false), Is.EqualTo(3000));
770+
}
771+
772+
[PlaywrightTest("page-clock.spec.ts", "should use the system time zone by default")]
773+
[Test]
774+
[Timeout(TestConstants.DefaultTestTimeout)]
775+
public async Task ShouldUseTheSystemTimeZoneByDefault()
776+
{
777+
await SkipUnlessTemporalAsync().ConfigureAwait(false);
778+
IBrowserContext context = await _browser.NewContextAsync(new BrowserNewContextOptions { TimezoneId = "America/Los_Angeles" }).ConfigureAwait(false);
779+
IPage page = await context.NewPageAsync().ConfigureAwait(false);
780+
await page.Clock.SetFixedTimeAsync(new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)).ConfigureAwait(false);
781+
await page.GoToAsync("data:text/html,").ConfigureAwait(false);
782+
Dictionary<string, string> result = await page.EvaluateAsync<Dictionary<string, string>>(
783+
@"() => {
784+
const Temporal = globalThis.Temporal;
785+
return {
786+
timeZoneId: Temporal.Now.timeZoneId(),
787+
zonedDateTime: Temporal.Now.zonedDateTimeISO().toString(),
788+
plainDateTime: Temporal.Now.plainDateTimeISO().toString(),
789+
};
790+
}").ConfigureAwait(false);
791+
Assert.That(result, Is.EqualTo(new Dictionary<string, string>
792+
{
793+
["timeZoneId"] = "America/Los_Angeles",
794+
["zonedDateTime"] = "2019-12-31T16:00:00-08:00[America/Los_Angeles]",
795+
["plainDateTime"] = "2019-12-31T16:00:00",
796+
}));
797+
await context.CloseAsync().ConfigureAwait(false);
798+
}
799+
800+
[PlaywrightTest("page-clock.spec.ts", "should keep the rest of Temporal intact")]
801+
[Test]
802+
[Timeout(TestConstants.DefaultTestTimeout)]
803+
public async Task ShouldKeepTheRestOfTemporalIntact()
804+
{
805+
await SkipUnlessTemporalAsync().ConfigureAwait(false);
806+
await _page.Clock.InstallAsync(0).ConfigureAwait(false);
807+
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
808+
JsonElement result = await _page.EvaluateAsync<JsonElement>(
809+
@"() => {
810+
const Temporal = globalThis.Temporal;
811+
return {
812+
tag: Object.prototype.toString.call(Temporal.Now),
813+
enumerable: Object.keys(Temporal).concat(Object.keys(Temporal.Now)),
814+
isInstant: Temporal.Now.instant() instanceof Temporal.Instant,
815+
duration: Temporal.Duration.from({ hours: 1 }).toString(),
816+
};
817+
}").ConfigureAwait(false);
818+
Assert.That(result.GetProperty("tag").GetString(), Is.EqualTo("[object Temporal.Now]"));
819+
Assert.That(result.GetProperty("enumerable").GetArrayLength(), Is.Zero);
820+
Assert.That(result.GetProperty("isInstant").GetBoolean(), Is.True);
821+
Assert.That(result.GetProperty("duration").GetString(), Is.EqualTo("PT1H"));
822+
}
823+
730824
[PlaywrightTest("page-clock.spec.ts", "AbortSignal.timeout")]
731825
[Test]
732826
[Timeout(TestConstants.DefaultTestTimeout)]
@@ -801,6 +895,14 @@ private async Task InstallPausedAsync()
801895
await _page.Clock.PauseAtAsync(1000).ConfigureAwait(false);
802896
}
803897

898+
private async Task SkipUnlessTemporalAsync()
899+
{
900+
if (!await _page.EvaluateAsync<bool>("() => 'Temporal' in globalThis").ConfigureAwait(false))
901+
{
902+
Assert.Ignore("Temporal is not supported");
903+
}
904+
}
905+
804906
private long[] CallNumbers()
805907
{
806908
long[] values = new long[_calls.Count];

‎src/PlaywrightNative/Helpers/clockSource.js‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,24 @@ function createIntl(clock, NativeIntl) {
448448
ClockIntl.DateTimeFormat.supportedLocalesOf = NativeIntl.DateTimeFormat.supportedLocalesOf;
449449
return ClockIntl;
450450
}
451+
function createTemporal(clock, NativeTemporal) {
452+
const NativeNow = NativeTemporal.Now;
453+
const instant = () => NativeTemporal.Instant.fromEpochMilliseconds(Math.trunc(clock.now()));
454+
const zonedDateTimeISO = (timeZone) => instant().toZonedDateTimeISO(timeZone != null ? timeZone : NativeNow.timeZoneId());
455+
const overrides = {
456+
instant,
457+
zonedDateTimeISO,
458+
plainDateTimeISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainDateTime(),
459+
plainDateISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainDate(),
460+
plainTimeISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainTime()
461+
};
462+
const ClockNow = Object.create(Object.getPrototypeOf(NativeNow), Object.getOwnPropertyDescriptors(NativeNow));
463+
for (const [key, value] of Object.entries(overrides))
464+
Object.defineProperty(ClockNow, key, { value, writable: true, enumerable: false, configurable: true });
465+
const ClockTemporal = Object.create(Object.getPrototypeOf(NativeTemporal), Object.getOwnPropertyDescriptors(NativeTemporal));
466+
Object.defineProperty(ClockTemporal, "Now", { value: ClockNow, writable: true, enumerable: false, configurable: true });
467+
return ClockTemporal;
468+
}
451469
function compareTimers(a, b) {
452470
if (a.callAt < b.callAt)
453471
return -1;
@@ -481,7 +499,8 @@ function platformOriginals(globalObject) {
481499
Date: globalObject.Date,
482500
performance: globalObject.performance,
483501
Intl: globalObject.Intl,
484-
AbortSignal: globalObject.AbortSignal
502+
AbortSignal: globalObject.AbortSignal,
503+
Temporal: globalObject.Temporal
485504
};
486505
const bound = { ...raw };
487506
for (const key of Object.keys(bound)) {
@@ -551,7 +570,8 @@ function createApi(clock, originals, browserName) {
551570
Intl: originals.Intl ? createIntl(clock, originals.Intl) : void 0,
552571
Date: createDate(clock, originals.Date),
553572
performance: originals.performance ? fakePerformance(clock, originals.performance) : void 0,
554-
AbortSignal: originals.AbortSignal ? fakeAbortSignal(clock, originals.AbortSignal, browserName) : void 0
573+
AbortSignal: originals.AbortSignal ? fakeAbortSignal(clock, originals.AbortSignal, browserName) : void 0,
574+
Temporal: originals.Temporal ? createTemporal(clock, originals.Temporal) : void 0
555575
};
556576
}
557577
function getClearHandler(type) {
@@ -653,12 +673,16 @@ function install(globalObject, config = {}) {
653673
const { clock, api, originals } = createClock(globalObject, config);
654674
const toFake = ((_b = config.toFake) == null ? void 0 : _b.length) ? config.toFake : Object.keys(originals);
655675
for (const method of toFake) {
676+
if (method === "Temporal" && !api.Temporal)
677+
continue;
656678
if (method === "Date") {
657679
globalObject.Date = mirrorDateProperties(api.Date, globalObject.Date);
658680
} else if (method === "Intl") {
659681
globalObject.Intl = api[method];
660682
} else if (method === "AbortSignal") {
661683
globalObject.AbortSignal = api[method];
684+
} else if (method === "Temporal") {
685+
globalObject.Temporal = api[method];
662686
} else if (method === "performance") {
663687
globalObject.performance = api[method];
664688
const kEventTimeStamp = Symbol("playwrightEventTimeStamp");

0 commit comments

Comments
 (0)