Skip to content
Closed
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
74 changes: 74 additions & 0 deletions playwright/src/main/java/com/microsoft/playwright/Clock.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.microsoft.playwright;

import java.util.Date;
import java.time.Instant;

/**
* Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn more
Expand Down Expand Up @@ -53,6 +54,13 @@ public InstallOptions setTime(Date time) {
this.time = time;
return this;
}
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(Instant time) {
this.time = time;
return this;
}
}
/**
* Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the
Expand Down Expand Up @@ -257,6 +265,39 @@ default void install() {
* @since v1.45
*/
void pauseAt(Date time);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
void pauseAt(Instant time);
/**
* Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
*
Expand Down Expand Up @@ -317,6 +358,24 @@ default void install() {
* @since v1.45
*/
void setFixedTime(Date time);
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(Instant time);
/**
* Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
Expand Down Expand Up @@ -362,5 +421,20 @@ default void install() {
* @since v1.45
*/
void setSystemTime(Date time);
/**
* Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(Instant time);
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.gson.JsonObject;
import com.microsoft.playwright.Clock;

import java.time.Instant;
import java.util.Date;

import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
Expand Down Expand Up @@ -72,9 +73,12 @@ public void pauseAt(String time) {

@Override
public void pauseAt(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("pauseAt", params);
pauseAt(time.getTime());
}

@Override
public void pauseAt(Instant time) {
pauseAt(time.toEpochMilli());
}

@Override
Expand All @@ -98,9 +102,12 @@ public void setFixedTime(String time) {

@Override
public void setFixedTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("setFixedTime", params);
setFixedTime(time.getTime());
}

@Override
public void setFixedTime(Instant time) {
setFixedTime(time.toEpochMilli());
}

@Override
Expand All @@ -119,16 +126,21 @@ public void setSystemTime(String time) {

@Override
public void setSystemTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("setSystemTime", params);
setSystemTime(time.getTime());
}

@Override
public void setSystemTime(Instant time) {
setSystemTime(time.toEpochMilli());
}

private static void parseTime(Object time, JsonObject params) {
if (time instanceof Long) {
params.addProperty("timeNumber", (Long) time);
} else if (time instanceof Date) {
params.addProperty("timeNumber", ((Date) time).getTime());
} else if (time instanceof Instant) {
params.addProperty("timeNumber", ((Instant) time).toEpochMilli());
} else if (time instanceof String) {
params.addProperty("timeString", (String) time);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;

Expand Down Expand Up @@ -350,6 +353,25 @@ void setFixedTimeAllowsInstallingFakeTimersAfterSettingTime(Page page) {
assertEquals(200, ((Object[]) calls.get(0))[0]);
}

@Test
void acceptsInstant(Page page) {
// A LocalDateTime has no time zone, so convert it to an Instant at an explicit offset.
page.clock().install(new Clock.InstallOptions().setTime(
LocalDateTime.of(2024, 12, 10, 8, 0, 0).toInstant(ZoneOffset.UTC)));

Instant paused = LocalDateTime.of(2024, 12, 10, 10, 0, 0).toInstant(ZoneOffset.UTC);
page.clock().pauseAt(paused);
assertEquals(paused.toEpochMilli(), ((Number) page.evaluate("() => Date.now()")).longValue());

Instant system = LocalDateTime.of(2024, 12, 10, 12, 0, 0).toInstant(ZoneOffset.UTC);
page.clock().setSystemTime(system);
assertEquals(system.toEpochMilli(), ((Number) page.evaluate("() => Date.now()")).longValue());

Instant fixed = LocalDateTime.of(2024, 12, 10, 14, 0, 0).toInstant(ZoneOffset.UTC);
page.clock().setFixedTime(fixed);
assertEquals(fixed.toEpochMilli(), ((Number) page.evaluate("() => Date.now()")).longValue());
}

@Test
void whileRunningShouldProgressTime(Page page) {
page.clock().install(new Clock.InstallOptions().setTime(0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,23 @@ private List<JsonObject> supportedUnionTypes() {
return result;
}

// The Clock time parameters are a union of number|string|Date. java.util.Date is a legacy type,
// so for Date-typed unions we additionally generate a java.time.Instant overload.
private static boolean hasDate(List<JsonObject> unionTypes) {
return unionTypes.stream().anyMatch(o -> "Date".equals(o.get("name").getAsString()));
}

int unionSize() {
return supportedUnionTypes().size();
List<JsonObject> types = supportedUnionTypes();
return types.size() + (hasDate(types) ? 1 : 0);
}

String formatTypeFromUnion(int i) {
JsonElement overloadedType = supportedUnionTypes().get(i);
return convertBuiltinType(overloadedType.getAsJsonObject());
List<JsonObject> types = supportedUnionTypes();
if (i == types.size() && hasDate(types)) {
return "Instant";
}
return convertBuiltinType(types.get(i));
}

boolean isNullable() {
Expand Down Expand Up @@ -1012,6 +1022,7 @@ void writeTo(List<String> output, String offset) {
}
if ("Clock".equals(jsonName)) {
output.add("import java.util.Date;");
output.add("import java.time.Instant;");
}
if (asList("Page", "Frame", "ElementHandle", "Locator", "LocatorAssertions", "APIRequest", "Browser", "BrowserContext", "BrowserType", "Route", "Request", "Response", "JSHandle", "ConsoleMessage", "APIResponse", "Playwright", "Debugger", "Screencast", "WebSocketRoute", "Credentials", "WebStorage").contains(jsonName)) {
output.add("import java.util.*;");
Expand Down
Loading