chore: roll to 1.59.0-alpha-1774622285000 (#1901)

This commit is contained in:
Yury Semikhatsky 2026-03-27 12:31:06 -07:00 committed by GitHub
parent afd80add27
commit 5ccdd3e4b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 528 additions and 75 deletions

View File

@ -10,7 +10,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->146.0.7680.31<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

View File

@ -35,11 +35,11 @@ public interface Debugger {
void offPausedStateChanged(Runnable handler);
/**
* Returns details about the currently paused calls. Returns an empty array if the debugger is not paused.
* Returns details about the currently paused call. Returns {@code null} if the debugger is not paused.
*
* @since v1.59
*/
List<PausedDetails> pausedDetails();
PausedDetails pausedDetails();
/**
* Configures the debugger to pause before the next action is executed.
*
@ -47,13 +47,13 @@ public interface Debugger {
* com.microsoft.playwright.Debugger#runTo Debugger.runTo()} to step while paused.
*
* <p> Note that {@link com.microsoft.playwright.Page#pause Page.pause()} is equivalent to a "debugger" statement it pauses
* execution at the call site immediately. On the contrary, {@link com.microsoft.playwright.Debugger#pause
* Debugger.pause()} is equivalent to "pause on next statement" it configures the debugger to pause before the next
* action is executed.
* execution at the call site immediately. On the contrary, {@link com.microsoft.playwright.Debugger#requestPause
* Debugger.requestPause()} is equivalent to "pause on next statement" it configures the debugger to pause before the
* next action is executed.
*
* @since v1.59
*/
void pause();
void requestPause();
/**
* Resumes script execution. Throws if the debugger is not paused.
*

View File

@ -35,8 +35,8 @@ public interface Locator {
*/
public Integer depth;
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption with element references. Defaults to {@code
* "default"}.
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption. Defaults to {@code "default"}. See details
* for more information.
*/
public AriaSnapshotMode mode;
/**
@ -55,8 +55,8 @@ public interface Locator {
return this;
}
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption with element references. Defaults to {@code
* "default"}.
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption. Defaults to {@code "default"}. See details
* for more information.
*/
public AriaSnapshotOptions setMode(AriaSnapshotMode mode) {
this.mode = mode;
@ -2322,6 +2322,12 @@ public interface Locator {
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* <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>
* </ol>
*
* @since v1.49
*/
default String ariaSnapshot() {
@ -2353,6 +2359,12 @@ public interface Locator {
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* <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>
* </ol>
*
* @since v1.49
*/
String ariaSnapshot(AriaSnapshotOptions options);

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
/**
* Interface for managing page overlays that display persistent visual indicators on top of the page.
*/
public interface Overlay {
class ShowOptions {
/**
* Duration in milliseconds after which the overlay is automatically removed. Overlay stays until dismissed if not
* provided.
*/
public Double duration;
/**
* Duration in milliseconds after which the overlay is automatically removed. Overlay stays until dismissed if not
* provided.
*/
public ShowOptions setDuration(double duration) {
this.duration = duration;
return this;
}
}
class ChapterOptions {
/**
* Optional description text displayed below the title.
*/
public String description;
/**
* Duration in milliseconds after which the overlay is automatically removed. Defaults to {@code 2000}.
*/
public Double duration;
/**
* Optional description text displayed below the title.
*/
public ChapterOptions setDescription(String description) {
this.description = description;
return this;
}
/**
* Duration in milliseconds after which the overlay is automatically removed. Defaults to {@code 2000}.
*/
public ChapterOptions setDuration(double duration) {
this.duration = duration;
return this;
}
}
/**
* Adds an overlay with the given HTML content. The overlay is displayed on top of the page until removed. Returns a
* disposable that removes the overlay when disposed.
*
* @param html HTML content for the overlay.
* @since v1.59
*/
default AutoCloseable show(String html) {
return show(html, null);
}
/**
* Adds an overlay with the given HTML content. The overlay is displayed on top of the page until removed. Returns a
* disposable that removes the overlay when disposed.
*
* @param html HTML content for the overlay.
* @since v1.59
*/
AutoCloseable show(String html, ShowOptions options);
/**
* Shows a chapter overlay with a title and optional description, centered on the page with a blurred backdrop. Useful for
* narrating video recordings. The overlay is removed after the specified duration, or 2000ms.
*
* @param title Title text displayed prominently in the overlay.
* @since v1.59
*/
default void chapter(String title) {
chapter(title, null);
}
/**
* Shows a chapter overlay with a title and optional description, centered on the page with a blurred backdrop. Useful for
* narrating video recordings. The overlay is removed after the specified duration, or 2000ms.
*
* @param title Title text displayed prominently in the overlay.
* @since v1.59
*/
void chapter(String title, ChapterOptions options);
/**
* Sets visibility of all overlays without removing them.
*
* @param visible Whether overlays should be visible.
* @since v1.59
*/
void setVisible(boolean visible);
}

View File

@ -2986,8 +2986,8 @@ public interface Page extends AutoCloseable {
*/
public Integer depth;
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption with element references. Defaults to {@code
* "default"}.
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption: including element references like {@code
* [ref=e2]} and snapshots of {@code <iframe>}s. Defaults to {@code "default"}.
*/
public AriaSnapshotMode mode;
/**
@ -3006,8 +3006,8 @@ public interface Page extends AutoCloseable {
return this;
}
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption with element references. Defaults to {@code
* "default"}.
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption: including element references like {@code
* [ref=e2]} and snapshots of {@code <iframe>}s. Defaults to {@code "default"}.
*/
public AriaSnapshotOptions setMode(AriaSnapshotMode mode) {
this.mode = mode;
@ -5880,6 +5880,12 @@ public interface Page extends AutoCloseable {
* @since v1.8
*/
Mouse mouse();
/**
*
*
* @since v1.59
*/
Overlay overlay();
/**
* Adds one-off {@code Dialog} handler. The handler will be removed immediately after next {@code Dialog} is created.
* <pre>{@code

View File

@ -805,7 +805,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
ResponseImpl response = connection.getExistingObject(guid);
response.request.existingResponse = response;
listeners.notify(EventType.RESPONSE, response);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());

View File

@ -16,21 +16,19 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Debugger;
import com.microsoft.playwright.options.Location;
import com.microsoft.playwright.options.PausedDetails;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
class DebuggerImpl extends ChannelOwner implements Debugger {
private final List<Runnable> pausedStateChangedHandlers = new ArrayList<>();
private List<PausedDetails> pausedDetails = new ArrayList<>();
private PausedDetails pausedDetails;
DebuggerImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@ -39,8 +37,11 @@ class DebuggerImpl extends ChannelOwner implements Debugger {
@Override
protected void handleEvent(String event, JsonObject params) {
if ("pausedStateChanged".equals(event)) {
JsonArray details = params.getAsJsonArray("pausedDetails");
pausedDetails = Arrays.asList(gson().fromJson(details, PausedDetails[].class));
if (params.has("pausedDetails") && !params.get("pausedDetails").isJsonNull()) {
pausedDetails = gson().fromJson(params.get("pausedDetails"), PausedDetails.class);
} else {
pausedDetails = null;
}
for (Runnable handler : new ArrayList<>(pausedStateChangedHandlers)) {
handler.run();
}
@ -58,13 +59,13 @@ class DebuggerImpl extends ChannelOwner implements Debugger {
}
@Override
public List<PausedDetails> pausedDetails() {
public PausedDetails pausedDetails() {
return pausedDetails;
}
@Override
public void pause() {
sendMessage("pause", new JsonObject(), NO_TIMEOUT);
public void requestPause() {
sendMessage("requestPause", new JsonObject(), NO_TIMEOUT);
}
@Override

View File

@ -43,7 +43,11 @@ class DialogImpl extends ChannelOwner implements Dialog {
@Override
public void dismiss() {
sendMessage("dismiss");
try {
sendMessage("dismiss");
} catch (TargetClosedError e) {
// Swallow TargetClosedErrors for beforeunload dialogs.
}
}
@Override

View File

@ -16,6 +16,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
@ -82,7 +84,7 @@ public class HARRouter {
if (status == -1) {
return;
}
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
Map<String, String> headers = mergeSetCookieHeaders(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
.setStatus(status)
@ -105,6 +107,25 @@ public class HARRouter {
route.abort();
}
private static Map<String, String> mergeSetCookieHeaders(JsonArray headersArray) {
Map<String, String> result = new java.util.LinkedHashMap<>();
for (JsonElement element : headersArray) {
JsonObject pair = element.getAsJsonObject();
String name = pair.get("name").getAsString();
String value = pair.get("value").getAsString();
if ("set-cookie".equalsIgnoreCase(name)) {
if (!result.containsKey("set-cookie")) {
result.put("set-cookie", value);
} else {
result.put("set-cookie", result.get("set-cookie") + "\n" + value);
}
} else {
result.put(name, value);
}
}
return result;
}
void dispose() {
JsonObject params = new JsonObject();
params.addProperty("harId", harId);

View File

@ -34,13 +34,20 @@ public class LocalUtils extends ChannelOwner {
return initializer.getAsJsonArray("deviceDescriptors");
}
void zip(Path zipFile, JsonArray entries, String stacksId, boolean appendMode, boolean includeSources) {
void zip(Path zipFile, JsonArray entries, String stacksId, boolean appendMode, boolean includeSources, List<String> additionalSources) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
params.add("entries", entries);
params.addProperty("mode", appendMode ? "append" : "write");
params.addProperty("stacksId", stacksId);
params.addProperty("includeSources", includeSources);
if (!additionalSources.isEmpty()) {
JsonArray sourcesArray = new JsonArray();
for (String source : additionalSources) {
sourcesArray.add(source);
}
params.add("additionalSources", sourcesArray);
}
sendMessage("zip", params, NO_TIMEOUT);
}

View File

@ -112,8 +112,8 @@ class LocatorImpl implements Locator {
public Locator normalize() {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("normalizeLocator", params, ChannelOwner.NO_TIMEOUT).getAsJsonObject();
return new LocatorImpl(frame, result.get("selector").getAsString(), null);
JsonObject result = frame.sendMessage("resolveSelector", params, ChannelOwner.NO_TIMEOUT).getAsJsonObject();
return new LocatorImpl(frame, result.get("resolvedSelector").getAsString(), null);
}
@Override

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Overlay;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class OverlayImpl implements Overlay {
private final ChannelOwner page;
OverlayImpl(ChannelOwner page) {
this.page = page;
}
@Override
public AutoCloseable show(String html, ShowOptions options) {
JsonObject params = new JsonObject();
params.addProperty("html", html);
if (options != null && options.duration != null) {
params.addProperty("duration", options.duration);
}
JsonObject result = (JsonObject) page.sendMessage("overlayShow", params, NO_TIMEOUT);
String id = result.get("id").getAsString();
return () -> {
JsonObject removeParams = new JsonObject();
removeParams.addProperty("id", id);
page.sendMessage("overlayRemove", removeParams, NO_TIMEOUT);
};
}
@Override
public void chapter(String title, ChapterOptions options) {
JsonObject params = new JsonObject();
params.addProperty("title", title);
if (options != null) {
if (options.description != null) {
params.addProperty("description", options.description);
}
if (options.duration != null) {
params.addProperty("duration", options.duration);
}
}
page.sendMessage("overlayChapter", params, NO_TIMEOUT);
}
@Override
public void setVisible(boolean visible) {
JsonObject params = new JsonObject();
params.addProperty("visible", visible);
page.sendMessage("overlaySetVisible", params, NO_TIMEOUT);
}
}

View File

@ -46,6 +46,7 @@ public class PageImpl extends ChannelOwner implements Page {
private final KeyboardImpl keyboard;
private final MouseImpl mouse;
private final TouchscreenImpl touchscreen;
private final OverlayImpl overlay;
final Waitable<?> waitableClosedOrCrashed;
private ViewportSize viewport;
private final Router routes = new Router();
@ -135,6 +136,7 @@ public class PageImpl extends ChannelOwner implements Page {
keyboard = new KeyboardImpl(this);
mouse = new MouseImpl(this);
touchscreen = new TouchscreenImpl(this);
overlay = new OverlayImpl(this);
frames.add(mainFrame);
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
waitableClosedOrCrashed = createWaitForCloseHelper();
@ -1347,6 +1349,11 @@ public class PageImpl extends ChannelOwner implements Page {
return touchscreen;
}
@Override
public Overlay overlay() {
return overlay;
}
@Override
public void type(String selector, String text, TypeOptions options) {
mainFrame.type(selector, text, convertType(options, Frame.TypeOptions.class));

View File

@ -42,6 +42,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
super(parent, type, guid, initializer);
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
request.existingResponse = this;
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
}
@ -88,10 +89,8 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public String httpVersion() {
if (initializer.has("httpVersion")) {
return initializer.get("httpVersion").getAsString();
}
return null;
JsonObject result = sendMessage("httpVersion").getAsJsonObject();
return result.get("value").getAsString();
}
@Override

View File

@ -489,7 +489,7 @@ class Serialization {
public JsonElement serialize(ConsoleMessagesFilter src, Type typeOfSrc, JsonSerializationContext context) {
switch (src) {
case ALL: return new JsonPrimitive("all");
case SINCE_NAVIGATION: return new JsonPrimitive("sinceNavigation");
case SINCE_NAVIGATION: return new JsonPrimitive("since-navigation");
default: throw new PlaywrightException("Unknown ConsoleMessagesFilter: " + src);
}
}

View File

@ -21,6 +21,10 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Tracing;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.microsoft.playwright.impl.Serialization.gson;
@ -29,6 +33,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
private Path tracesDir;
private boolean isTracing;
private String stacksId;
private final Set<String> additionalSources = new HashSet<>();
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@ -42,6 +47,9 @@ class TracingImpl extends ChannelOwner implements Tracing {
}
JsonObject params = new JsonObject();
List<String> capturedAdditionalSources = new ArrayList<>(additionalSources);
additionalSources.clear();
// Not interested in artifacts.
if (path == null) {
params.addProperty("mode", "discard");
@ -57,7 +65,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
params.addProperty("mode", "entries");
JsonObject json = sendMessage("tracingStopChunk", params, NO_TIMEOUT).getAsJsonObject();
JsonArray entries = json.getAsJsonArray("entries");
connection.localUtils.zip(path, entries, stacksId, false, includeSources);
connection.localUtils.zip(path, entries, stacksId, false, includeSources, capturedAdditionalSources);
return;
}
@ -74,7 +82,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
artifact.saveAs(path);
artifact.delete();
connection.localUtils.zip(path, new JsonArray(), stacksId, true, includeSources);
connection.localUtils.zip(path, new JsonArray(), stacksId, true, includeSources, capturedAdditionalSources);
}
@Override
@ -95,6 +103,9 @@ class TracingImpl extends ChannelOwner implements Tracing {
if (options == null) {
options = new GroupOptions();
}
if (options.location != null) {
additionalSources.add(options.location.file);
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
sendMessage("tracingGroup", params, NO_TIMEOUT);

View File

@ -28,6 +28,7 @@ import java.nio.file.Paths;
class VideoImpl implements Video {
private final PageImpl page;
private ArtifactImpl artifact;
private Path savePath;
VideoImpl(PageImpl page) {
this.page = page;
@ -58,7 +59,13 @@ class VideoImpl implements Video {
public AutoCloseable start(StartOptions options) {
JsonObject params = new JsonObject();
if (options != null) {
params = gson().toJsonTree(options).getAsJsonObject();
if (options.size != null) {
params.add("size", gson().toJsonTree(options.size));
}
if (options.annotate != null) {
params.add("annotate", gson().toJsonTree(options.annotate));
}
savePath = options.path;
}
JsonObject result = page.sendMessage("videoStart", params, ChannelOwner.NO_TIMEOUT).getAsJsonObject();
String artifactGuid = result.getAsJsonObject("artifact").get("guid").getAsString();
@ -69,6 +76,10 @@ class VideoImpl implements Video {
@Override
public void stop() {
page.sendMessage("videoStop", new JsonObject(), ChannelOwner.NO_TIMEOUT);
if (savePath != null) {
saveAs(savePath);
savePath = null;
}
}
@Override

View File

@ -20,13 +20,35 @@ public class Annotate {
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
public Integer delay;
public Double duration;
/**
* Position of the action title overlay. Defaults to {@code "top-right"}.
*/
public AnnotatePosition position;
/**
* Font size of the action title in pixels. Defaults to {@code 24}.
*/
public Integer fontSize;
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
public Annotate setDelay(int delay) {
this.delay = delay;
public Annotate setDuration(double duration) {
this.duration = duration;
return this;
}
/**
* Position of the action title overlay. Defaults to {@code "top-right"}.
*/
public Annotate setPosition(AnnotatePosition position) {
this.position = position;
return this;
}
/**
* Font size of the action title in pixels. Defaults to {@code 24}.
*/
public Annotate setFontSize(int fontSize) {
this.fontSize = fontSize;
return this;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum AnnotatePosition {
TOP_LEFT,
TOP,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM,
BOTTOM_RIGHT
}

View File

@ -19,61 +19,83 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.PausedDetails;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class TestDebugger extends TestBase {
@Test
void shouldReturnEmptyPausedDetailsInitially() {
void shouldReturnNullPausedDetailsInitially() {
Debugger dbg = context.debugger();
assertEquals(Collections.emptyList(), dbg.pausedDetails());
}
@Test
void shouldPauseAtPauseCall() {
page.setContent("<div>click me</div>");
Debugger dbg = context.debugger();
assertEquals(Collections.emptyList(), dbg.pausedDetails());
dbg.pause();
boolean[] paused = {false};
dbg.onPausedStateChanged(() -> {
if (!paused[0]) {
paused[0] = true;
List<PausedDetails> details = dbg.pausedDetails();
assertEquals(1, details.size());
assertTrue(details.get(0).title.contains("Pause"), "title: " + details.get(0).title);
dbg.resume();
}
});
page.pause(); // blocks until dbg.resume() is called from event handler
assertEquals(Collections.emptyList(), dbg.pausedDetails());
assertNull(dbg.pausedDetails());
}
@Test
void shouldPauseAtNextAndResume() {
page.setContent("<div>click me</div>");
Debugger dbg = context.debugger();
assertEquals(Collections.emptyList(), dbg.pausedDetails());
assertNull(dbg.pausedDetails());
dbg.pause();
dbg.requestPause();
boolean[] paused = {false};
dbg.onPausedStateChanged(() -> {
if (!paused[0]) {
paused[0] = true;
List<PausedDetails> details = dbg.pausedDetails();
assertEquals(1, details.size());
assertTrue(details.get(0).title.contains("Click"), "title: " + details.get(0).title);
PausedDetails details = dbg.pausedDetails();
assertNotNull(details);
assertTrue(details.title.contains("Click"), "title: " + details.title);
dbg.resume();
}
});
page.click("div"); // blocks until dbg.resume() is called
assertEquals(Collections.emptyList(), dbg.pausedDetails());
assertNull(dbg.pausedDetails());
}
@Test
void shouldStepWithNext() {
page.setContent("<div>click me</div>");
Debugger dbg = context.debugger();
assertNull(dbg.pausedDetails());
dbg.requestPause();
boolean[] paused = {false};
dbg.onPausedStateChanged(() -> {
if (!paused[0]) {
paused[0] = true;
PausedDetails details = dbg.pausedDetails();
assertNotNull(details);
assertTrue(details.title.contains("Click"), "title: " + details.title);
dbg.next();
} else if (dbg.pausedDetails() != null) {
dbg.resume();
}
});
page.click("div");
assertNull(dbg.pausedDetails());
}
@Test
void shouldPauseAtPauseCall() {
page.setContent("<div>click me</div>");
Debugger dbg = context.debugger();
assertNull(dbg.pausedDetails());
dbg.requestPause();
boolean[] paused = {false};
dbg.onPausedStateChanged(() -> {
if (!paused[0]) {
paused[0] = true;
PausedDetails details = dbg.pausedDetails();
assertNotNull(details);
assertTrue(details.title.contains("Pause"), "title: " + details.title);
dbg.resume();
}
});
page.pause(); // blocks until dbg.resume() is called from event handler
assertNull(dbg.pausedDetails());
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.Overlay;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
// Note: The overlay elements are rendered inside a closed shadow root in driver mode,
// so locator-based DOM assertions are not possible in Java. These tests verify that
// the protocol calls succeed without errors.
public class TestOverlay extends TestBase {
@Test
void shouldAddAndRemoveOverlay() throws Exception {
page.navigate(server.EMPTY_PAGE);
AutoCloseable disposable = page.overlay().show("<div id=\"my-overlay\">Hello Overlay</div>");
assertNotNull(disposable);
disposable.close();
}
@Test
void shouldAddMultipleOverlays() throws Exception {
page.navigate(server.EMPTY_PAGE);
AutoCloseable d1 = page.overlay().show("<div id=\"overlay-1\">First</div>");
AutoCloseable d2 = page.overlay().show("<div id=\"overlay-2\">Second</div>");
d1.close();
d2.close();
}
@Test
void shouldHideAndShowOverlays() throws Exception {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"my-overlay\">Visible</div>");
page.overlay().setVisible(false);
page.overlay().setVisible(true);
}
@Test
void shouldSurviveNavigation() {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"persistent\">Survives Reload</div>");
page.navigate(server.EMPTY_PAGE);
page.reload();
}
@Test
void shouldRemoveOverlayAndNotRestoreAfterNavigation() throws Exception {
page.navigate(server.EMPTY_PAGE);
AutoCloseable disposable = page.overlay().show("<div id=\"temp\">Temporary</div>");
disposable.close();
page.reload();
}
@Test
void shouldSanitizeScriptsFromOverlayHtml() {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"safe\">Safe</div><script>window.__injected = true</script>");
}
@Test
void shouldStripEventHandlersFromOverlayHtml() {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"clean\" onclick=\"window.__clicked=true\">Click me</div>");
}
@Test
void shouldAutoRemoveOverlayAfterTimeout() {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"timed\">Temporary</div>", new Overlay.ShowOptions().setDuration(1));
}
@Test
void shouldAllowStylesInOverlayHtml() {
page.navigate(server.EMPTY_PAGE);
page.overlay().show("<div id=\"styled\" style=\"color: red; font-size: 20px;\">Styled</div>");
}
@Test
void shouldShowChapter() {
page.navigate(server.EMPTY_PAGE);
page.overlay().chapter("Chapter Title");
page.overlay().chapter("With Description", new Overlay.ChapterOptions().setDescription("Some details").setDuration(100));
}
}

View File

@ -71,6 +71,35 @@ public class TestPageNetworkResponse extends TestBase {
assertTrue(e.getMessage().contains("closed"), e.getMessage());
}
@Test
void shouldReturnNullExistingResponseBeforeResponseReceived() {
Request[] capturedRequest = {null};
page.route("**/*", route -> {
capturedRequest[0] = route.request();
assertNull(capturedRequest[0].existingResponse());
route.resume();
});
page.navigate(server.EMPTY_PAGE);
assertNotNull(capturedRequest[0]);
}
@Test
void shouldReturnExistingResponseAfterReceived() {
Response[] responses = {null};
page.onResponse(r -> responses[0] = r);
page.navigate(server.EMPTY_PAGE);
assertNotNull(responses[0]);
assertEquals(responses[0], responses[0].request().existingResponse());
}
@Test
void shouldReturnHttpVersion() {
page.navigate(server.EMPTY_PAGE);
Response response = page.waitForResponse("**/*", () -> page.navigate(server.EMPTY_PAGE));
String version = response.httpVersion();
assertTrue(version.matches("HTTP/[12](\\.[01])?"), "unexpected version: " + version);
}
@Test
void shouldRejectResponseFinishedIfContextCloses() {
page.navigate(server.EMPTY_PAGE);

View File

@ -1 +1 @@
1.59.0-alpha-1774287265000
1.59.0-alpha-1774622285000