Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,100 @@ public async Task CheckDateNowIsAnInteger2()
Assert.That(await _page.EvaluateAsync<long>("Date.now()").ConfigureAwait(false), Is.EqualTo(1001));
}

[PlaywrightTest("page-clock.spec.ts", "should follow fixed time")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldFollowFixedTime()
{
await SkipUnlessTemporalAsync().ConfigureAwait(false);
await _page.Clock.SetFixedTimeAsync(new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)).ConfigureAwait(false);
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
Dictionary<string, string> result = await _page.EvaluateAsync<Dictionary<string, string>>(
@"() => {
const Temporal = globalThis.Temporal;
return {
instant: Temporal.Now.instant().toString(),
zonedDateTime: Temporal.Now.zonedDateTimeISO('UTC').toString(),
plainDateTime: Temporal.Now.plainDateTimeISO('UTC').toString(),
plainDate: Temporal.Now.plainDateISO('UTC').toString(),
plainTime: Temporal.Now.plainTimeISO('UTC').toString(),
};
}").ConfigureAwait(false);
Assert.That(result, Is.EqualTo(new Dictionary<string, string>
{
["instant"] = "2020-01-01T00:00:00Z",
["zonedDateTime"] = "2020-01-01T00:00:00+00:00[UTC]",
["plainDateTime"] = "2020-01-01T00:00:00",
["plainDate"] = "2020-01-01",
["plainTime"] = "00:00:00",
}));
}

[PlaywrightTest("page-clock.spec.ts", "should advance with the clock")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldAdvanceWithTheClock()
{
await SkipUnlessTemporalAsync().ConfigureAwait(false);
await _page.Clock.InstallAsync(0).ConfigureAwait(false);
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
await _page.Clock.PauseAtAsync(1000).ConfigureAwait(false);
await _page.Clock.RunForAsync(2000).ConfigureAwait(false);
Assert.That(await _page.EvaluateAsync<long>("() => globalThis.Temporal.Now.instant().epochMilliseconds").ConfigureAwait(false), Is.EqualTo(3000));
}

[PlaywrightTest("page-clock.spec.ts", "should use the system time zone by default")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldUseTheSystemTimeZoneByDefault()
{
await SkipUnlessTemporalAsync().ConfigureAwait(false);
IBrowserContext context = await _browser.NewContextAsync(new BrowserNewContextOptions { TimezoneId = "America/Los_Angeles" }).ConfigureAwait(false);
IPage page = await context.NewPageAsync().ConfigureAwait(false);
await page.Clock.SetFixedTimeAsync(new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)).ConfigureAwait(false);
await page.GoToAsync("data:text/html,").ConfigureAwait(false);
Dictionary<string, string> result = await page.EvaluateAsync<Dictionary<string, string>>(
@"() => {
const Temporal = globalThis.Temporal;
return {
timeZoneId: Temporal.Now.timeZoneId(),
zonedDateTime: Temporal.Now.zonedDateTimeISO().toString(),
plainDateTime: Temporal.Now.plainDateTimeISO().toString(),
};
}").ConfigureAwait(false);
Assert.That(result, Is.EqualTo(new Dictionary<string, string>
{
["timeZoneId"] = "America/Los_Angeles",
["zonedDateTime"] = "2019-12-31T16:00:00-08:00[America/Los_Angeles]",
["plainDateTime"] = "2019-12-31T16:00:00",
}));
await context.CloseAsync().ConfigureAwait(false);
}

[PlaywrightTest("page-clock.spec.ts", "should keep the rest of Temporal intact")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldKeepTheRestOfTemporalIntact()
{
await SkipUnlessTemporalAsync().ConfigureAwait(false);
await _page.Clock.InstallAsync(0).ConfigureAwait(false);
await _page.GoToAsync("data:text/html,").ConfigureAwait(false);
JsonElement result = await _page.EvaluateAsync<JsonElement>(
@"() => {
const Temporal = globalThis.Temporal;
return {
tag: Object.prototype.toString.call(Temporal.Now),
enumerable: Object.keys(Temporal).concat(Object.keys(Temporal.Now)),
isInstant: Temporal.Now.instant() instanceof Temporal.Instant,
duration: Temporal.Duration.from({ hours: 1 }).toString(),
};
}").ConfigureAwait(false);
Assert.That(result.GetProperty("tag").GetString(), Is.EqualTo("[object Temporal.Now]"));
Assert.That(result.GetProperty("enumerable").GetArrayLength(), Is.Zero);
Assert.That(result.GetProperty("isInstant").GetBoolean(), Is.True);
Assert.That(result.GetProperty("duration").GetString(), Is.EqualTo("PT1H"));
}

[PlaywrightTest("page-clock.spec.ts", "AbortSignal.timeout")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
Expand Down Expand Up @@ -801,6 +895,14 @@ private async Task InstallPausedAsync()
await _page.Clock.PauseAtAsync(1000).ConfigureAwait(false);
}

private async Task SkipUnlessTemporalAsync()
{
if (!await _page.EvaluateAsync<bool>("() => 'Temporal' in globalThis").ConfigureAwait(false))
{
Assert.Ignore("Temporal is not supported");
}
}

private long[] CallNumbers()
{
long[] values = new long[_calls.Count];
Expand Down
28 changes: 26 additions & 2 deletions src/PlaywrightNative/Helpers/clockSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,24 @@ function createIntl(clock, NativeIntl) {
ClockIntl.DateTimeFormat.supportedLocalesOf = NativeIntl.DateTimeFormat.supportedLocalesOf;
return ClockIntl;
}
function createTemporal(clock, NativeTemporal) {
const NativeNow = NativeTemporal.Now;
const instant = () => NativeTemporal.Instant.fromEpochMilliseconds(Math.trunc(clock.now()));
const zonedDateTimeISO = (timeZone) => instant().toZonedDateTimeISO(timeZone != null ? timeZone : NativeNow.timeZoneId());
const overrides = {
instant,
zonedDateTimeISO,
plainDateTimeISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainDateTime(),
plainDateISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainDate(),
plainTimeISO: (timeZone) => zonedDateTimeISO(timeZone).toPlainTime()
};
const ClockNow = Object.create(Object.getPrototypeOf(NativeNow), Object.getOwnPropertyDescriptors(NativeNow));
for (const [key, value] of Object.entries(overrides))
Object.defineProperty(ClockNow, key, { value, writable: true, enumerable: false, configurable: true });
const ClockTemporal = Object.create(Object.getPrototypeOf(NativeTemporal), Object.getOwnPropertyDescriptors(NativeTemporal));
Object.defineProperty(ClockTemporal, "Now", { value: ClockNow, writable: true, enumerable: false, configurable: true });
return ClockTemporal;
}
function compareTimers(a, b) {
if (a.callAt < b.callAt)
return -1;
Expand Down Expand Up @@ -481,7 +499,8 @@ function platformOriginals(globalObject) {
Date: globalObject.Date,
performance: globalObject.performance,
Intl: globalObject.Intl,
AbortSignal: globalObject.AbortSignal
AbortSignal: globalObject.AbortSignal,
Temporal: globalObject.Temporal
};
const bound = { ...raw };
for (const key of Object.keys(bound)) {
Expand Down Expand Up @@ -551,7 +570,8 @@ function createApi(clock, originals, browserName) {
Intl: originals.Intl ? createIntl(clock, originals.Intl) : void 0,
Date: createDate(clock, originals.Date),
performance: originals.performance ? fakePerformance(clock, originals.performance) : void 0,
AbortSignal: originals.AbortSignal ? fakeAbortSignal(clock, originals.AbortSignal, browserName) : void 0
AbortSignal: originals.AbortSignal ? fakeAbortSignal(clock, originals.AbortSignal, browserName) : void 0,
Temporal: originals.Temporal ? createTemporal(clock, originals.Temporal) : void 0
};
}
function getClearHandler(type) {
Expand Down Expand Up @@ -653,12 +673,16 @@ function install(globalObject, config = {}) {
const { clock, api, originals } = createClock(globalObject, config);
const toFake = ((_b = config.toFake) == null ? void 0 : _b.length) ? config.toFake : Object.keys(originals);
for (const method of toFake) {
if (method === "Temporal" && !api.Temporal)
continue;
if (method === "Date") {
globalObject.Date = mirrorDateProperties(api.Date, globalObject.Date);
} else if (method === "Intl") {
globalObject.Intl = api[method];
} else if (method === "AbortSignal") {
globalObject.AbortSignal = api[method];
} else if (method === "Temporal") {
globalObject.Temporal = api[method];
} else if (method === "performance") {
globalObject.performance = api[method];
const kEventTimeStamp = Symbol("playwrightEventTimeStamp");
Expand Down
Loading