diff --git a/README.md b/README.md index 3c891060..d3a74878 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 121.0.6167.57 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 122.0.6261.29 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | ✅ | ✅ | ✅ | -| Firefox 121.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 122.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details. diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index 14f2cf6d..a65762ee 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -60,7 +60,7 @@ public interface BrowserContext extends AutoCloseable { /** * Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code - * console.dir}. Also emitted if the page throws an error or a warning. + * console.dir}. * *
The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler * argument. diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index cd7cb3ca..b89cf893 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -1779,8 +1779,8 @@ public interface ElementHandle extends JSHandle { *
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate * different respective texts. * - *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with - * the modifier, modifier is pressed and being held while the subsequent key is being pressed. + *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as + * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed. * * @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}. * @since v1.8 @@ -1809,8 +1809,8 @@ public interface ElementHandle extends JSHandle { *
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate * different respective texts. * - *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with - * the modifier, modifier is pressed and being held while the subsequent key is being pressed. + *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as + * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed. * * @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}. * @since v1.8 diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index d1aef5da..6e1ed559 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -3991,8 +3991,8 @@ public interface Frame { *
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate * different respective texts. * - *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with - * the modifier, modifier is pressed and being held while the subsequent key is being pressed. + *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as + * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed. * * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. * @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}. @@ -4020,8 +4020,8 @@ public interface Frame { *
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate * different respective texts. * - *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with - * the modifier, modifier is pressed and being held while the subsequent key is being pressed. + *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as + * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed. * * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. * @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}. diff --git a/playwright/src/main/java/com/microsoft/playwright/Keyboard.java b/playwright/src/main/java/com/microsoft/playwright/Keyboard.java index 2951f33c..3fb93a7a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Keyboard.java +++ b/playwright/src/main/java/com/microsoft/playwright/Keyboard.java @@ -152,8 +152,8 @@ public interface Keyboard { *
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate * different respective texts. * - *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with - * the modifier, modifier is pressed and being held while the subsequent key is being pressed. + *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as + * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed. * *
**Usage** *
{@code
@@ -197,8 +197,8 @@ public interface Keyboard {
* If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
- *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
- * the modifier, modifier is pressed and being held while the subsequent key is being pressed.
+ *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
+ * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
*
**Usage**
*
{@code
diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java
index 7c6d1dbd..ba19bc0d 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Locator.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java
@@ -4084,8 +4084,8 @@ public interface Locator {
* If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
- *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
- * the modifier, modifier is pressed and being held while the subsequent key is being pressed.
+ *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
+ * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
@@ -4123,8 +4123,8 @@ public interface Locator {
*
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
- *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
- * the modifier, modifier is pressed and being held while the subsequent key is being pressed.
+ *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
+ * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java
index 7259936f..7535d615 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Page.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Page.java
@@ -80,7 +80,7 @@ public interface Page extends AutoCloseable {
/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
- * console.dir}. Also emitted if the page throws an error or a warning.
+ * console.dir}.
*
*
The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
*
@@ -5804,8 +5804,8 @@ public interface Page extends AutoCloseable {
*
If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
- *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
- * the modifier, modifier is pressed and being held while the subsequent key is being pressed.
+ *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
+ * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
*
**Usage**
*
{@code
@@ -5847,8 +5847,8 @@ public interface Page extends AutoCloseable {
* If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
- *
Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
- * the modifier, modifier is pressed and being held while the subsequent key is being pressed.
+ *
Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
+ * well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
*
**Usage**
*
{@code
@@ -5895,6 +5895,77 @@ public interface Page extends AutoCloseable {
* @since v1.9
*/
List querySelectorAll(String selector);
+ /**
+ * Sometimes, the web page can show an overlay that obstructs elements behind it and prevents certain actions, like click,
+ * from completing. When such an overlay is shown predictably, we recommend dismissing it as a part of your test flow.
+ * However, sometimes such an overlay may appear non-deterministically, for example certain cookies consent dialogs behave
+ * this way. In this case, {@link Page#addLocatorHandler Page.addLocatorHandler()} allows handling an overlay during an
+ * action that it would block.
+ *
+ * This method registers a handler for an overlay that is executed once the locator is visible on the page. The handler
+ * should get rid of the overlay so that actions blocked by it can proceed. This is useful for nondeterministic
+ * interstitial pages or dialogs, like a cookie consent dialog.
+ *
+ *
Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
+ *
+ *
You can register multiple handlers. However, only a single handler will be running at a time. Any actions inside a
+ * handler must not require another handler to run.
+ *
+ *
NOTE: Running the interceptor will alter your page state mid-test. For example it will change the currently focused element
+ * and move the mouse. Make sure that the actions that run after the interceptor are self-contained and do not rely on the
+ * focus and mouse state.
For example, consider a test that calls {@link Locator#focus Locator.focus()}
+ * followed by {@link Keyboard#press Keyboard.press()}. If your handler clicks a button between these two actions, the
+ * focused element most likely will be wrong, and key press will happen on the unexpected element. Use {@link Locator#press
+ * Locator.press()} instead to avoid this problem.
Another example is a series of mouse actions, where {@link
+ * Mouse#move Mouse.move()} is followed by {@link Mouse#down Mouse.down()}. Again, when the handler runs between these two
+ * actions, the mouse position will be wrong during the mouse down. Prefer methods like {@link Locator#click
+ * Locator.click()} that are self-contained.
+ *
+ *
**Usage**
+ *
+ *
An example that closes a cookie dialog when it appears:
+ *
{@code
+ * // Setup the handler.
+ * page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
+ * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
+ * });
+ *
+ * // Write the test as usual.
+ * page.goto("https://example.com");
+ * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
+ * }
+ *
+ * An example that skips the "Confirm your security details" page when it is shown:
+ *
{@code
+ * // Setup the handler.
+ * page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
+ * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
+ * });
+ *
+ * // Write the test as usual.
+ * page.goto("https://example.com");
+ * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
+ * }
+ *
+ * An example with a custom callback on every actionability check. It uses a {@code
} locator that is always visible,
+ * so the handler is called before every actionability check:
+ * {@code
+ * // Setup the handler.
+ * page.addLocatorHandler(page.locator("body")), () => {
+ * page.evaluate("window.removeObstructionsForTestIfNeeded()");
+ * });
+ *
+ * // Write the test as usual.
+ * page.goto("https://example.com");
+ * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
+ * }
+ *
+ * @param locator Locator that triggers the handler.
+ * @param handler Function that should be run once {@code locator} appears. This function should get rid of the element that blocks
+ * actions like click.
+ * @since v1.42
+ */
+ void addLocatorHandler(Locator locator, Runnable handler);
/**
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
index c93089ed..b1f06f9b 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java
@@ -186,7 +186,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
throw new PlaywrightException("Failed to read from file", e);
}
String content = new String(encoded, StandardCharsets.UTF_8);
- content += "//# sourceURL=" + options.path.toString().replace("\n", "");
+ content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java b/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java
index d547917b..10b7c590 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java
@@ -73,6 +73,14 @@ public class HARRouter {
if ("fulfill".equals(action)) {
int status = response.get("status").getAsInt();
+ // If the response status is -1, the request was canceled or stalled, so we just stall it here.
+ // See https://github.com/microsoft/playwright/issues/29311.
+ // TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
+ // because the request might have been stalled for a long time until the very end of the
+ // test when HAR was recorded but we'd abort it immediately.
+ if (status == -1) {
+ return;
+ }
Map headers = fromNameValues(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
index 873842bd..d0206786 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
@@ -49,6 +49,8 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set frames = new LinkedHashSet<>();
+ private final Map locatorHandlers = new HashMap<>();
+
private static final Map eventSubscriptions() {
Map result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
@@ -170,6 +172,9 @@ public class PageImpl extends ChannelOwner implements Page {
frame.parentFrame.childFrames.remove(frame);
}
listeners.notify(EventType.FRAMEDETACHED, frame);
+ } else if ("locatorHandlerTriggered".equals(event)) {
+ int uid = params.get("uid").getAsInt();
+ onLocatorHandlerTriggered(uid);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
@@ -523,6 +528,34 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}
+ @Override
+ public void addLocatorHandler(Locator locator, Runnable handler) {
+ LocatorImpl locatorImpl = (LocatorImpl) locator;
+ if (locatorImpl.frame != mainFrame) {
+ throw new PlaywrightException("Locator must belong to the main frame of this page");
+ }
+ withLogging("Page.addLocatorHandler", () -> {
+ JsonObject params = new JsonObject();
+ params.addProperty("selector", locatorImpl.selector);
+ JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
+ int uid = json.get("uid").getAsInt();
+ locatorHandlers.put(uid, handler);
+ });
+ }
+
+ private void onLocatorHandlerTriggered(int uid) {
+ try {
+ Runnable handler = locatorHandlers.get(uid);
+ if (handler != null) {
+ handler.run();
+ }
+ } finally {
+ JsonObject params = new JsonObject();
+ params.addProperty("uid", uid);
+ sendMessageAsync("resolveLocatorHandlerNoReply", params);
+ }
+ }
+
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
@@ -544,7 +577,8 @@ public class PageImpl extends ChannelOwner implements Page {
withLogging("Page.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
- addInitScriptImpl(new String(bytes, UTF_8));
+ String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
+ addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
index 981a4249..33ffa12a 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java
@@ -341,4 +341,8 @@ public class Utils {
}
return flags;
}
+
+ static String addSourceUrlToScript(String source, Path path) {
+ return source + "\n//# sourceURL=" + path.toString().replace("\n", "");
+ }
}
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
index 83d6463a..d46aec96 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java
@@ -33,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.concurrent.Future;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.copy;
@@ -469,4 +470,37 @@ public class TestBrowserContextHar extends TestBase {
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
+
+ @Test
+ void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) {
+ Path path = tmpDir.resolve("test.har");
+ try (BrowserContext context1 = browser.newContext()) {
+ server.setRoute("/x", exchange -> exchange.close());
+ context1.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUpdate(true));
+ Page page1 = context1.newPage();
+ page1.navigate(server.EMPTY_PAGE);
+ Future reqPromise = server.futureRequest("/x");
+ Object req = page1.evaluate("url => fetch(url).catch(e => 'cancelled')", server.PREFIX + "/x");
+ assertEquals("cancelled", req);
+ }
+ server.reset();
+
+ try (BrowserContext context2 = browser.newContext()) {
+ server.setRoute("/x", exchange -> {
+ exchange.getResponseHeaders().add("content-type", "text/html");
+ exchange.sendResponseHeaders(200, 4);
+ try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
+ writer.write("test");
+ }
+ });
+ context2.routeFromHAR(path);
+ Page page2 = context2.newPage();
+ page2.navigate(server.EMPTY_PAGE);
+ page2.evaluate("url => {\n" +
+ " fetch(url).catch(e => 'cancelled').then(r => { window.result = r; })\n" +
+ "}", server.PREFIX + "/x");
+ page2.waitForTimeout(1000);
+ assertNull(page.evaluate("window.result"));
+ }
+ }
}
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
index 3d1a1a27..0d423330 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java
@@ -151,7 +151,7 @@ public class TestBrowserContextStorageState extends TestBase {
" origin: 'http://localhost:" + server.PORT + "',\n" +
" localStorage: [{\n" +
" name: 'foo',\n" +
- " value: '" + (char)55934 + "'\n" +
+ " value: '" + (char)65533 + "'\n" +
" }]\n" +
"}]}", new Gson().fromJson(storageState, JsonObject.class));
}
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
index 6307e244..9becc7ef 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java
@@ -66,7 +66,7 @@ public class TestBrowserTypeConnect extends TestBase {
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.driverPath().getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
- String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
+ String cliJs = dir.resolve("package/cli.js").toString();
// We launch node process directly instead of using playwright.sh script as killing the script
// process will leave node process running and killing it would be more hassle.
ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name());
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageAddLocatorHandler.java b/playwright/src/test/java/com/microsoft/playwright/TestPageAddLocatorHandler.java
new file mode 100644
index 00000000..22b50234
--- /dev/null
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPageAddLocatorHandler.java
@@ -0,0 +1,156 @@
+package com.microsoft.playwright;
+
+import org.junit.jupiter.api.Test;
+
+import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TestPageAddLocatorHandler extends TestBase {
+ @Test
+ void shouldWork() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ int[] beforeCount = {0};
+ int[] afterCount = {0};
+
+ page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
+ ++beforeCount[0];
+ page.locator("#close").click();
+ ++afterCount[0];
+ });
+
+ String[][] argsList = {
+ {"mouseover", "1"},
+ {"mouseover", "1", "capture"},
+ {"mouseover", "2"},
+ {"mouseover", "2", "capture"},
+ {"pointerover", "1"},
+ {"pointerover", "1", "capture"},
+ {"none", "1"},
+ {"remove", "1"},
+ {"hide", "1"},
+ };
+
+ for (String[] args : argsList) {
+ page.locator("#aside").hover();
+ beforeCount[0] = 0;
+ afterCount[0] = 0;
+
+ page.evaluate("(args) => {\n" +
+ " window.clicked = 0;\n" +
+ " window.setupAnnoyingInterstitial(...args);\n" +
+ "}", args);
+
+ assertEquals(0, beforeCount[0]);
+ assertEquals(0, afterCount[0]);
+
+ page.locator("#target").click();
+
+ assertEquals(Integer.parseInt(args[1]), beforeCount[0]);
+ assertEquals(Integer.parseInt(args[1]), afterCount[0]);
+ assertEquals(1, page.evaluate("window.clicked"));
+ assertThat(page.locator("#interstitial")).not().isVisible();
+ }
+ }
+
+ @Test
+ void shouldWorkWithCustomCheck() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ page.addLocatorHandler(page.locator("body"), () -> {
+ if (page.getByText("This interstitial covers the button").isVisible())
+ page.locator("#close").click();
+ });
+
+ String[][] argsList = {
+ {"mouseover", "2"},
+ {"none", "1"},
+ {"remove", "1"},
+ {"hide", "1"},
+ };
+
+ for (String[] args : argsList) {
+ page.hover("#aside");
+ page.evaluate("(args) => {\n" +
+ " window.clicked = 0;\n" +
+ " window.setupAnnoyingInterstitial(...args);\n" +
+ "}", args);
+
+ page.locator("#target").click();
+
+ assertEquals(1, page.evaluate("window.clicked"));
+ assertThat(page.locator("#interstitial")).not().isVisible();
+ }
+ }
+
+ @Test
+ void shouldWorkWithLocatorHover() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
+ page.locator("#close").click();
+ });
+
+ page.locator("#aside").hover();
+ page.evaluate("() => {\n" +
+ " window.setupAnnoyingInterstitial('pointerover', 1, 'capture');\n" +
+ " }");
+ page.locator("#target").hover();
+ assertThat(page.locator("#interstitial")).not().isVisible();
+ assertEquals("rgb(255, 255, 0)", page.evalOnSelector("#target", "element => getComputedStyle(element).backgroundColor"));
+ }
+
+ @Test
+ void shouldNotWorkWithForceTrue() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
+ page.locator("#close").click();
+ });
+
+ page.locator("#aside").hover();
+ page.evaluate("() => {\n" +
+ " window.setupAnnoyingInterstitial('none', 1);\n" +
+ " }");
+
+ page.locator("#target").click(new Locator.ClickOptions().setForce(true).setTimeout(2000));
+ assertTrue(page.locator("#interstitial").isVisible());
+ assertNull(page.evaluate("window.clicked"));
+ }
+
+ @Test
+ void shouldThrowWhenPageCloses() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
+ page.close();
+ });
+
+ page.locator("#aside").hover();
+ page.evaluate("() => {\n" +
+ " window.clicked = 0;\n" +
+ " window.setupAnnoyingInterstitial('mouseover', 1);\n" +
+ " }");
+ PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.locator("#target").click());
+ assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
+ }
+
+ @Test
+ void shouldWorkWithToBeVisible() {
+ page.navigate(server.PREFIX + "/input/handle-locator.html");
+
+ int[] called = {0};
+ page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
+ ++called[0];
+ page.locator("#close").click();
+ });
+
+ page.evaluate("() => {\n" +
+ " window.clicked = 0;\n" +
+ " window.setupAnnoyingInterstitial('remove', 1);\n" +
+ "}");
+ assertThat(page.locator("#target")).isVisible();
+ assertThat(page.locator("#interstitial")).not().isVisible();
+ assertEquals(1, called[0]);
+ }
+}
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageAddScriptTag.java b/playwright/src/test/java/com/microsoft/playwright/TestPageAddScriptTag.java
new file mode 100644
index 00000000..2e8da0e4
--- /dev/null
+++ b/playwright/src/test/java/com/microsoft/playwright/TestPageAddScriptTag.java
@@ -0,0 +1,21 @@
+package com.microsoft.playwright;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIf;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestPageAddScriptTag extends TestBase {
+ @Test
+ @DisabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="Upstream behavior")
+ void shouldIncludeSourceURLWhenPathIsProvided() {
+ page.navigate(server.EMPTY_PAGE);
+ Path path = Paths.get("src/test/resources/injectedfile.js");
+ page.addScriptTag(new Page.AddScriptTagOptions().setPath(path));
+ String result = (String) page.evaluate("() => window['__injectedError'].stack");
+ assertTrue(result.contains("resources/injectedfile.js"));
+ }
+}
diff --git a/playwright/src/test/resources/input/handle-locator.html b/playwright/src/test/resources/input/handle-locator.html
new file mode 100644
index 00000000..865fb536
--- /dev/null
+++ b/playwright/src/test/resources/input/handle-locator.html
@@ -0,0 +1,82 @@
+
+
+
+ Interstitial test
+
+
+
+
+ A place on the side to hover
+
+ This interstitial covers the button
+
+
+
+
+
diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION
index 7d47e599..1a36b31f 100644
--- a/scripts/CLI_VERSION
+++ b/scripts/CLI_VERSION
@@ -1 +1 @@
-1.41.0
+1.42.0-alpha-1707967288000