From 7f2a5fe9af51f5c359bf453e7be0de83013d947f Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 15 Feb 2024 08:49:45 -0800 Subject: [PATCH] feat: roll 1.42.0-alpha driver, implement page.addLocatorHandler (#1494) --- README.md | 4 +- .../microsoft/playwright/BrowserContext.java | 2 +- .../microsoft/playwright/ElementHandle.java | 8 +- .../java/com/microsoft/playwright/Frame.java | 8 +- .../com/microsoft/playwright/Keyboard.java | 8 +- .../com/microsoft/playwright/Locator.java | 8 +- .../java/com/microsoft/playwright/Page.java | 81 ++++++++- .../microsoft/playwright/impl/FrameImpl.java | 2 +- .../microsoft/playwright/impl/HARRouter.java | 8 + .../microsoft/playwright/impl/PageImpl.java | 36 +++- .../com/microsoft/playwright/impl/Utils.java | 4 + .../playwright/TestBrowserContextHar.java | 34 ++++ .../TestBrowserContextStorageState.java | 2 +- .../playwright/TestBrowserTypeConnect.java | 2 +- .../playwright/TestPageAddLocatorHandler.java | 156 ++++++++++++++++++ .../playwright/TestPageAddScriptTag.java | 21 +++ .../test/resources/input/handle-locator.html | 82 +++++++++ scripts/CLI_VERSION | 2 +- 18 files changed, 439 insertions(+), 29 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageAddLocatorHandler.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageAddScriptTag.java create mode 100644 playwright/src/test/resources/input/handle-locator.html diff --git a/README.md b/README.md index 3c891060d..d3a748788 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 14f2cf6d3..a65762eee 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 cd7cb3caf..b89cf893e 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 @@ default String inputValue() { *

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 @@ default void press(String key) { *

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 d1aef5da6..6e1ed559f 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 @@ default Locator locator(String selector) { *

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 @@ default void press(String selector, String key) { *

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 2951f33c5..3fb93a7aa 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 TypeOptions setDelay(double delay) { *

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 @@ default void press(String key) {
    * 

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 7c6d1dbdd..ba19bc0dd 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 @@ default Locator locator(Locator selectorOrLocator) {
    * 

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 @@ default void press(String key) { *

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 7259936ff..7535d6151 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 @@ default byte[] pdf() { *

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 @@ default void press(String selector, String key) {
    * 

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 @@ default ElementHandle querySelector(String selector) {
    * @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 c93089ede..b1f06f9b4 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 @@ ElementHandle addScriptTagImpl(AddScriptTagOptions options) { 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 d547917bd..10b7c590b 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 @@ void handle(Route route) { 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 873842bde..d02067869 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 @@ protected void handleEvent(String event, JsonObject params) { 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 List querySelectorAll(String selector) { 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 void addInitScript(Path path) { 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 981a4249f..33ffa12a7 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 @@ static int fromJsRegexFlags(String regexFlags) { } 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 83d6463a5..d46aec960 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.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 @@ void shouldUpdateExtractedHarZipForPage(@TempDir Path tmpDir) { 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 3d1a1a270..0d4233300 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 @@ void shouldSerialiseStorageStateWithLoneSurrogates() { " 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 6307e2446..9becc7efd 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 @@ private static BrowserServer launchBrowserServer(BrowserType browserType) { 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 000000000..22b502347 --- /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 000000000..2e8da0e4d --- /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 000000000..865fb5364 --- /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 7d47e5998..1a36b31f4 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.41.0 +1.42.0-alpha-1707967288000