Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->156.0.8078.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->158.0.8127.18<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->27.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->157.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->159.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

## Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1388,12 +1388,12 @@ default void routeFromHAR(Path har) {
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if ("to-be-blocked".equals(frame.text()))
* return;
* ws.send(message);
* server.send(frame.text());
* });
* ws.connect();
* });
* }</pre>
*
Expand All @@ -1415,12 +1415,12 @@ default void routeFromHAR(Path har) {
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if ("to-be-blocked".equals(frame.text()))
* return;
* ws.send(message);
* server.send(frame.text());
* });
* ws.connect();
* });
* }</pre>
*
Expand All @@ -1442,12 +1442,12 @@ default void routeFromHAR(Path har) {
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if ("to-be-blocked".equals(frame.text()))
* return;
* ws.send(message);
* server.send(frame.text());
* });
* ws.connect();
* });
* }</pre>
*
Expand Down
30 changes: 20 additions & 10 deletions playwright/src/main/java/com/microsoft/playwright/BrowserType.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ class ConnectOverCDPOptions {
public @Nullable Boolean isLocal;
/**
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
* to {@code false}.
* acceptDownloads} and font families are left at the browser's settings, focus emulation is not enabled, and media
* emulation options (such as {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are
* not applied. Useful when attaching to a user's daily-driver browser where these overrides would interfere with existing
* browser state. New contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not
* affected. Defaults to {@code false}.
*/
public @Nullable Boolean noDefaults;
/**
Expand Down Expand Up @@ -182,11 +182,11 @@ public ConnectOverCDPOptions setIsLocal(boolean isLocal) {
}
/**
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
* to {@code false}.
* acceptDownloads} and font families are left at the browser's settings, focus emulation is not enabled, and media
* emulation options (such as {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are
* not applied. Useful when attaching to a user's daily-driver browser where these overrides would interfere with existing
* browser state. New contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not
* affected. Defaults to {@code false}.
*/
public ConnectOverCDPOptions setNoDefaults(boolean noDefaults) {
this.noDefaults = noDefaults;
Expand Down Expand Up @@ -1360,6 +1360,11 @@ default Browser connect(String endpoint) {
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or {@code
* ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
*
* <p> Alternatively, a browser channel name such as {@code chrome} or {@code msedge} to connect to a browser with remote
* debugging enabled in its default user data directory. On Linux, Playwright uses {@code CHROME_USER_DATA_DIR} when set.
* Otherwise, it uses {@code CHROME_CONFIG_HOME}, then {@code XDG_CONFIG_HOME}, then {@code ~/.config} to locate that
* directory.
* @since v1.9
*/
default Browser connectOverCDP(String endpointURL) {
Expand Down Expand Up @@ -1389,6 +1394,11 @@ default Browser connectOverCDP(String endpointURL) {
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or {@code
* ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
*
* <p> Alternatively, a browser channel name such as {@code chrome} or {@code msedge} to connect to a browser with remote
* debugging enabled in its default user data directory. On Linux, Playwright uses {@code CHROME_USER_DATA_DIR} when set.
* Otherwise, it uses {@code CHROME_CONFIG_HOME}, then {@code XDG_CONFIG_HOME}, then {@code ~/.config} to locate that
* directory.
* @since v1.9
*/
Browser connectOverCDP(String endpointURL, @Nullable ConnectOverCDPOptions options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,37 @@ public interface CDPSession {
*/
void offClose(Consumer<CDPSession> handler);

class SendOptions {
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 0} - no timeout.
*/
public @Nullable Double timeout;

/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 0} - no timeout.
*/
public SendOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Detaches the CDPSession from the target. Once detached, the CDPSession object won't emit any events and can't be used to
* send messages.
*
* @since v1.8
*/
void detach();
/**
*
*
* @param method Protocol method name.
* @param args Optional method parameters.
* @since v1.8
*/
default JsonObject send(String method, @Nullable JsonObject args) {
return send(method, args, null);
}
/**
*
*
Expand All @@ -82,7 +106,7 @@ default JsonObject send(String method) {
* @param args Optional method parameters.
* @since v1.8
*/
JsonObject send(String method, @Nullable JsonObject args);
JsonObject send(String method, @Nullable JsonObject args, @Nullable SendOptions options);
/**
* Register an event handler for events with the specified event name. The given handler will be called for every event
* with the given name.
Expand Down
16 changes: 8 additions & 8 deletions playwright/src/main/java/com/microsoft/playwright/Clock.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public InstallOptions setTime(Date time) {
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @param ticks Time may be a finite number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
* "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(long ticks);
Expand All @@ -80,8 +80,8 @@ public InstallOptions setTime(Date time) {
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @param ticks Time may be a finite number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
* "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(String ticks);
Expand Down Expand Up @@ -142,8 +142,8 @@ default void install() {
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @param ticks Time may be a finite number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
* "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(long ticks);
Expand All @@ -156,8 +156,8 @@ default void install() {
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @param ticks Time may be a finite number of milliseconds to advance the clock by or a human-readable string. Valid string formats are
* "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(String ticks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2586,7 +2586,9 @@ public WaitForFunctionOptions setTimeout(double timeout) {
* <p> An AI-optimized snapshot, controlled by {@code mode}, is different from a default snapshot:
* <ol>
* <li> Includes element references {@code [ref=e2]}. 2. Does not wait for an element matching the locator, and throws when no
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target.</li>
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target. 4. Can omit names that repeat printed
* descendants, such as a heading containing a link. Names that identify dialogs, landmarks, groups, tables and table
* headers are retained.</li>
* </ol>
*
* @since v1.49
Expand Down Expand Up @@ -2623,7 +2625,9 @@ default String ariaSnapshot() {
* <p> An AI-optimized snapshot, controlled by {@code mode}, is different from a default snapshot:
* <ol>
* <li> Includes element references {@code [ref=e2]}. 2. Does not wait for an element matching the locator, and throws when no
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target.</li>
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target. 4. Can omit names that repeat printed
* descendants, such as a heading containing a link. Names that identify dialogs, landmarks, groups, tables and table
* headers are retained.</li>
* </ol>
*
* @since v1.49
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public void dispose(DisposeOptions options) {
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params, NO_TIMEOUT);
tracing.resetStackCounter();
}

@Override
Expand Down Expand Up @@ -200,7 +201,7 @@ private static boolean isJsonContentType(Map<String, String> headers) {
}
for (Map.Entry<String, String> e : headers.entrySet()) {
if ("content-type".equalsIgnoreCase(e.getKey())) {
return "application/json".equals(e.getValue());
return "application/json".equals(e.getValue().split(";", -1)[0].trim().toLowerCase());
}
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,8 @@ void didClose() {
browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
}
tracing.resetStackCounter();
request.tracing().resetStackCounter();
listeners.notify(EventType.CLOSE, this);
}

Expand Down Expand Up @@ -945,7 +947,7 @@ protected void initializeHarFromOptions(Browser.NewContextOptions options) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.toString().endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
contentPolicy = TracingImpl.isZip(options.recordHarPath) ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ public JsonObject send(String method) {
return send(method, null);
}

public JsonObject send(String method, JsonObject params) {
@Override
public JsonObject send(String method, JsonObject params, SendOptions options) {
JsonObject args = new JsonObject();
if (params != null) {
args.add("params", params);
}
args.addProperty("method", method);
JsonElement response = connection.sendMessage(guid, "send", args);
Double timeout = options == null || options.timeout == null ? Double.valueOf(0) : options.timeout;
JsonElement response = connection.sendMessage(guid, "send", args, timeout);
if (response == null) return null;
else return response.getAsJsonObject().get("result").getAsJsonObject();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
String method;
JsonObject params;
JsonElement result;
SerializedError error;

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (playwright-x64-ubuntu24-64-core, chromium)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (playwright-x64-ubuntu24-64-core, webkit)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / stable (playwright-x64-ubuntu24-64-core, chrome)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (playwright-x64-ubuntu24-64-core, firefox)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (macos-26-xlarge, chromium)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (macos-26-xlarge, webkit)

cannot find symbol

Check failure on line 43 in playwright/src/main/java/com/microsoft/playwright/impl/Connection.java

View workflow job for this annotation

GitHub Actions / dev (macos-26-xlarge, firefox)

cannot find symbol
JsonObject errorDetails;
JsonArray log;

Expand Down Expand Up @@ -89,6 +89,7 @@
PlaywrightImpl initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
params.addProperty("supportsLoneSurrogates", true);
JsonElement result = sendMessage("initialize", params.getAsJsonObject(), NO_TIMEOUT);
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,15 @@ static class HarRecorder {
super(parent, type, guid, initializer);
}

private void stopChunkImpl(Path path) {
void resetStackCounter() {
if (isTracing) {
isTracing = false;
connection.setIsTracing(false);
}
}

private void stopChunkImpl(Path path) {
resetStackCounter();

List<String> capturedAdditionalSources = new ArrayList<>(additionalSources);
additionalSources.clear();
Expand Down Expand Up @@ -231,7 +235,7 @@ public AutoCloseable startHar(Path path, StartHarOptions options) {
if (options == null) {
options = new StartHarOptions();
}
boolean isZip = path.toString().endsWith(".zip");
boolean isZip = isZip(path);
HarContentPolicy contentPolicy = options.content != null
? options.content
: (isZip ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED);
Expand All @@ -250,6 +254,10 @@ public void stopHar() {
exportHar(harId);
}

static boolean isZip(Path path) {
return path.toString().toLowerCase().endsWith(".zip");
}

String recordIntoHar(PageImpl page, Path har, Object urlFilter, HarContentPolicy contentPolicy, HarMode mode, Path resourcesDir) {
if (contentPolicy == null) {
contentPolicy = HarContentPolicy.ATTACH;
Expand All @@ -263,14 +271,14 @@ String recordIntoHar(PageImpl page, Path har, Object urlFilter, HarContentPolicy
params.add("page", page.toProtocolRef());
}
JsonObject recordHarArgs = new JsonObject();
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
recordHarArgs.addProperty("zip", isZip(har));
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
recordHarArgs.addProperty("mode", mode.name().toLowerCase());
addHarUrlFilter(recordHarArgs, urlFilter);
if (resourcesDir != null) {
recordHarArgs.addProperty("resourcesDir", resourcesDir.toString());
}
if (!har.toString().endsWith(".zip")) {
if (!isZip(har)) {
recordHarArgs.addProperty("harPath", har.toString());
}

Expand All @@ -287,7 +295,7 @@ void exportHar(String harId) {
return;
}
boolean isLocal = !connection.isRemote;
boolean isZip = harParams.path.toString().endsWith(".zip");
boolean isZip = isZip(harParams.path);

JsonObject params = new JsonObject();
params.addProperty("harId", harId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ void shouldWork(Page page) {
assertEquals("bar", foo);
}

@PlaywrightTest
void shouldRespectTimeoutOption(Page page) {
CDPSession cdpSession = page.context().newCDPSession(page);
JsonObject params = new JsonObject();
params.addProperty("expression", "1 + 2");
params.addProperty("returnByValue", true);
JsonObject response = cdpSession.send("Runtime.evaluate", params, new CDPSession.SendOptions().setTimeout(5000));
assertEquals(3, response.getAsJsonObject("result").get("value").getAsInt());

JsonObject pending = new JsonObject();
pending.addProperty("expression", "new Promise(() => {})");
pending.addProperty("awaitPromise", true);
TimeoutError error = assertThrows(TimeoutError.class,
() -> cdpSession.send("Runtime.evaluate", pending, new CDPSession.SendOptions().setTimeout(500)));
assertTrue(error.getMessage().contains("Timeout 500ms exceeded"), error.getMessage());
}

@PlaywrightTest
void shouldSendEvents(Page page, Server server) {
CDPSession cdpSession = page.context().newCDPSession(page);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ void shouldSerialiseStorageStateWithLoneSurrogates(Page page, BrowserContext con
" origin: 'http://localhost:" + server.PORT + "',\n" +
" localStorage: [{\n" +
" name: 'foo',\n" +
" value: '" + (char)65533 + "'\n" +
" value: '" + (char)55934 + "'\n" +
" }]\n" +
"}]}", new Gson().fromJson(storageState, JsonObject.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ void cookiesShouldReturnCookiesFilteredByUrls(Playwright playwright, Server serv
request.addCookies(asList(
new Cookie("a", "b").setDomain("localhost").setPath("/"),
new Cookie("c", "d").setDomain("localhost").setPath("/input"),
new Cookie("e", "f").setDomain("one.com").setPath("/"),
new Cookie("e", "f").setDomain(".one.com").setPath("/"),
new Cookie("g", "h").setDomain("two.com").setPath("/").setSecure(true)));
assertEquals(asList("a", "c", "e", "g"), cookieNames(request.cookies()));
assertEquals(asList("a"), cookieNames(request.cookies(server.EMPTY_PAGE)));
Expand Down
Loading
Loading