From be31d8b8ce33eef8086fcfe01a87052346a1079b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 22 Jul 2026 12:24:36 -0700 Subject: [PATCH] chore: roll to 1.62.0-alpha-2026-07-22 Port upstream changes: - microsoft/playwright#41712 - timeout is now a protocol-level metadata field - microsoft/playwright#41380 - Locator.waitForFunction() - microsoft/playwright#41800 - APIResponse.timing() - microsoft/playwright#41433 - scroll option to opt out of autoscroll - microsoft/playwright#41152 - webp screenshot format - microsoft/playwright#41294 - routeFromHAR interceptAPIRequests option - microsoft/playwright#41898 - acknowledge screencast frames after callbacks - microsoft/playwright#41215 - webauthn credentials in storageState (generated) - microsoft/playwright#41518 - driver publishing removed upstream; download script now falls back to the latest Node.js LTS --- README.md | 4 +- examples/pom.xml | 2 +- .../com/microsoft/playwright/APIResponse.java | 10 + .../microsoft/playwright/BrowserContext.java | 51 +++- .../com/microsoft/playwright/BrowserType.java | 6 + .../com/microsoft/playwright/Credentials.java | 9 +- .../microsoft/playwright/ElementHandle.java | 137 +++++++++- .../java/com/microsoft/playwright/Frame.java | 136 ++++++++++ .../com/microsoft/playwright/Locator.java | 242 +++++++++++++++++- .../java/com/microsoft/playwright/Page.java | 164 +++++++++++- .../playwright/impl/APIResponseImpl.java | 21 ++ .../playwright/impl/BrowserContextImpl.java | 3 + .../playwright/impl/ChannelOwner.java | 5 +- .../microsoft/playwright/impl/Connection.java | 17 +- .../playwright/impl/ElementHandleImpl.java | 3 + .../microsoft/playwright/impl/HARRouter.java | 26 ++ .../playwright/impl/LocatorImpl.java | 11 + .../microsoft/playwright/impl/PageImpl.java | 3 + .../playwright/impl/ScreencastImpl.java | 22 +- .../playwright/impl/Serialization.java | 1 + .../playwright/options/ScreenshotType.java | 3 +- .../playwright/options/ScrollMode.java | 22 ++ .../microsoft/playwright/options/Timing.java | 22 +- .../playwright/TestBrowserContextFetch.java | 16 ++ .../playwright/TestBrowserContextHar.java | 58 +++++ .../playwright/TestLocatorClick.java | 23 +- .../microsoft/playwright/TestLocatorMisc.java | 45 ++++ .../playwright/TestPageAriaSnapshotAI.java | 5 +- .../playwright/TestPageScreenshot.java | 35 +++ scripts/DRIVER_VERSION | 2 +- scripts/download_driver.sh | 10 +- 31 files changed, 1060 insertions(+), 54 deletions(-) create mode 100644 playwright/src/main/java/com/microsoft/playwright/options/ScrollMode.java diff --git a/README.md b/README.md index e3e411c3b..08f5626c4 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 149.0.7827.55 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 151.0.7922.34 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 26.5 | ✅ | ✅ | ✅ | -| Firefox 151.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 152.0.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | ## Documentation diff --git a/examples/pom.xml b/examples/pom.xml index 3c2db93f1..012777b53 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ Playwright Client Examples UTF-8 - 1.61.0 + 1.62.0 diff --git a/playwright/src/main/java/com/microsoft/playwright/APIResponse.java b/playwright/src/main/java/com/microsoft/playwright/APIResponse.java index 13bd81064..c52f1d31e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/APIResponse.java +++ b/playwright/src/main/java/com/microsoft/playwright/APIResponse.java @@ -87,6 +87,16 @@ public interface APIResponse { * @since v1.16 */ String text(); + /** + * Returns resource timing information for given response. For redirected requests, returns the information for the last + * request in the redirect chain. When the response is served from the HAR file, timing information is not + * available and all the values are -1. Find more information at Resource Timing API. + * + * @since v1.62 + */ + Timing timing(); /** * Contains the URL of the response. * diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index b254cf5c2..49ace5531 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -363,6 +363,13 @@ public RouteOptions setTimes(int times) { } } class RouteFromHAROptions { + /** + * If set to {@code true}, requests made via {@code APIRequestContext} (such as {@link + * com.microsoft.playwright.BrowserContext#request BrowserContext.request()} or {@link + * com.microsoft.playwright.Page#request Page.request()}) are also served from the HAR file. By default these requests are + * sent to the network, matching the behavior prior to v1.62. Defaults to {@code false} for backward compatibility. + */ + public Boolean interceptAPIRequests; /** *
    *
  • If set to 'abort' any request not found in the HAR file will be aborted.
  • @@ -394,6 +401,16 @@ class RouteFromHAROptions { */ public Object url; + /** + * If set to {@code true}, requests made via {@code APIRequestContext} (such as {@link + * com.microsoft.playwright.BrowserContext#request BrowserContext.request()} or {@link + * com.microsoft.playwright.Page#request Page.request()}) are also served from the HAR file. By default these requests are + * sent to the network, matching the behavior prior to v1.62. Defaults to {@code false} for backward compatibility. + */ + public RouteFromHAROptions setInterceptAPIRequests(boolean interceptAPIRequests) { + this.interceptAPIRequests = interceptAPIRequests; + return this; + } /** *
      *
    • If set to 'abort' any request not found in the HAR file will be aborted.
    • @@ -449,6 +466,16 @@ public RouteFromHAROptions setUrl(Pattern url) { } } class StorageStateOptions { + /** + * Set to {@code true} to include the context's virtual WebAuthn {@link com.microsoft.playwright.BrowserContext#credentials + * BrowserContext.credentials()} (passkeys) in the storage state snapshot. The captured credentials carry their private + * keys, so they can be re-seeded into a later context via the {@code storageState} option or {@link + * com.microsoft.playwright.BrowserContext#setStorageState BrowserContext.setStorageState()}. Note that restoring the + * storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see {@link + * com.microsoft.playwright.Credentials#install Credentials.install()}), and prevent all real authenticators from working + * in this context. + */ + public Boolean credentials; /** * Set to {@code true} to include IndexedDB in * the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase @@ -461,6 +488,19 @@ class StorageStateOptions { */ public Path path; + /** + * Set to {@code true} to include the context's virtual WebAuthn {@link com.microsoft.playwright.BrowserContext#credentials + * BrowserContext.credentials()} (passkeys) in the storage state snapshot. The captured credentials carry their private + * keys, so they can be re-seeded into a later context via the {@code storageState} option or {@link + * com.microsoft.playwright.BrowserContext#setStorageState BrowserContext.setStorageState()}. Note that restoring the + * storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see {@link + * com.microsoft.playwright.Credentials#install Credentials.install()}), and prevent all real authenticators from working + * in this context. + */ + public StorageStateOptions setCredentials(boolean credentials) { + this.credentials = credentials; + return this; + } /** * Set to {@code true} to include IndexedDB in * the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase @@ -1469,7 +1509,8 @@ default void routeFromHAR(Path har) { */ void setOffline(boolean offline); /** - * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot. + * Returns storage state for this browser context, contains current cookies, local storage snapshot, IndexedDB snapshot and + * virtual WebAuthn credentials. * * @since v1.8 */ @@ -1477,13 +1518,17 @@ default String storageState() { return storageState(null); } /** - * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot. + * Returns storage state for this browser context, contains current cookies, local storage snapshot, IndexedDB snapshot and + * virtual WebAuthn credentials. * * @since v1.8 */ String storageState(StorageStateOptions options); /** - * Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state. + * Clears the existing cookies, local storage, IndexedDB entries and virtual WebAuthn credentials, and sets the new storage + * state. When the storage state contains credentials, the virtual WebAuthn authenticator is installed (equivalent to + * {@link com.microsoft.playwright.Credentials#install Credentials.install()}), preventing all real authenticators from + * working in this context. * *

      Usage *

      {@code
      diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
      index 2fa39586d..305dd7fc5 100644
      --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
      +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java
      @@ -1314,6 +1314,9 @@ default Browser connect(String endpoint) {
          * advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
          * BrowserType.connect()}.
          *
      +   * 

      NOTE: Playwright maintains a curated list of arguments for launching the browser. If you launch the browser without Playwright + * and do not pass the exact same arguments, some of Playwright functionality may be broken upon connecting to the browser. + * *

      Usage *

      {@code
          * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
      @@ -1340,6 +1343,9 @@ default Browser connectOverCDP(String endpointURL) {
          * advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
          * BrowserType.connect()}.
          *
      +   * 

      NOTE: Playwright maintains a curated list of arguments for launching the browser. If you launch the browser without Playwright + * and do not pass the exact same arguments, some of Playwright functionality may be broken upon connecting to the browser. + * *

      Usage *

      {@code
          * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
      diff --git a/playwright/src/main/java/com/microsoft/playwright/Credentials.java b/playwright/src/main/java/com/microsoft/playwright/Credentials.java
      index a3a180095..5c3eb6d40 100644
      --- a/playwright/src/main/java/com/microsoft/playwright/Credentials.java
      +++ b/playwright/src/main/java/com/microsoft/playwright/Credentials.java
      @@ -24,7 +24,7 @@
        * passkeys and answer {@code navigator.credentials.create()} / {@code navigator.credentials.get()} ceremonies in the page,
        * without a real authenticator or hardware security key.
        *
      - * 

      There are two common ways to use it: + *

      There are three common ways to use it: * *

      Usage: seed a known credential *

      {@code
      @@ -43,7 +43,7 @@
        * // The page's navigator.credentials.get() is answered with the seeded passkey.
        * }
      * - *

      Usage: capture a passkey, then reuse it + *

      Usage: capture a credential, then reuse it *

      {@code
        * // setup test: let the app register a passkey, then save it.
        * BrowserContext context = browser.newContext();
      @@ -75,6 +75,11 @@
        * // navigator.credentials.get() resolves the captured passkey — already signed in.
        * }
      * + *

      Usage: save credentials in the storage state, restore later + * + *

      See authentication guide for examples of using saving and resotring + * the storage state. + * *

      Defaults */ public interface Credentials { diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index 6d7c4621c..9fb77bcfa 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -73,6 +73,13 @@ class CheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -117,6 +124,16 @@ public CheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public CheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -170,6 +187,13 @@ class ClickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -250,6 +274,16 @@ public ClickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ClickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -308,6 +342,13 @@ class DblclickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -381,6 +422,16 @@ public DblclickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DblclickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -475,6 +526,13 @@ class HoverOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -528,6 +586,16 @@ public HoverOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public HoverOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -550,18 +618,12 @@ public HoverOptions setTrial(boolean trial) { } class InputValueOptions { /** - * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default - * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout - * BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()} - * methods. + * @deprecated This option is ignored. The value is returned immediately. */ public Double timeout; /** - * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default - * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout - * BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()} - * methods. + * @deprecated This option is ignored. The value is returned immediately. */ public InputValueOptions setTimeout(double timeout) { this.timeout = timeout; @@ -652,7 +714,9 @@ class ScreenshotOptions { */ public Path path; /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public Integer quality; /** @@ -740,7 +804,9 @@ public ScreenshotOptions setPath(Path path) { return this; } /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public ScreenshotOptions setQuality(int quality) { this.quality = quality; @@ -896,6 +962,13 @@ class SetCheckedOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -940,6 +1013,16 @@ public SetCheckedOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public SetCheckedOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1012,6 +1095,13 @@ class TapOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1065,6 +1155,16 @@ public TapOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public TapOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1142,6 +1242,13 @@ class UncheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1186,6 +1293,16 @@ public UncheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public UncheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index b00258a6b..82f6874bd 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -165,6 +165,13 @@ class CheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -214,6 +221,16 @@ public CheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public CheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -275,6 +292,13 @@ class ClickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -355,6 +379,16 @@ public ClickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ClickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -413,6 +447,13 @@ class DblclickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -486,6 +527,16 @@ public DblclickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DblclickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -558,6 +609,13 @@ class DragAndDropOptions { * @deprecated This option has no effect. */ public Boolean noWaitAfter; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -607,6 +665,16 @@ public DragAndDropOptions setNoWaitAfter(boolean noWaitAfter) { this.noWaitAfter = noWaitAfter; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DragAndDropOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -1156,6 +1224,13 @@ class HoverOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -1215,6 +1290,16 @@ public HoverOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public HoverOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -1772,6 +1857,13 @@ class SetCheckedOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -1821,6 +1913,16 @@ public SetCheckedOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public SetCheckedOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -1963,6 +2065,13 @@ class TapOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -2022,6 +2131,16 @@ public TapOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public TapOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -2154,6 +2273,13 @@ class UncheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -2203,6 +2329,16 @@ public UncheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public UncheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java index b0eb27f08..5b997bac9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Locator.java +++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java @@ -145,6 +145,13 @@ class CheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -189,6 +196,16 @@ public CheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public CheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -286,6 +303,13 @@ class ClickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -367,6 +391,16 @@ public ClickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ClickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -426,6 +460,13 @@ class DblclickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -500,6 +541,16 @@ public DblclickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DblclickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current * cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination @@ -560,6 +611,13 @@ class DragToOptions { * @deprecated This option has no effect. */ public Boolean noWaitAfter; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -604,6 +662,16 @@ public DragToOptions setNoWaitAfter(boolean noWaitAfter) { this.noWaitAfter = noWaitAfter; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DragToOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -1241,6 +1309,13 @@ class HoverOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1295,6 +1370,16 @@ public HoverOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public HoverOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1710,7 +1795,9 @@ class ScreenshotOptions { */ public Path path; /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public Integer quality; /** @@ -1798,7 +1885,9 @@ public ScreenshotOptions setPath(Path path) { return this; } /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public ScreenshotOptions setQuality(int quality) { this.quality = quality; @@ -1954,6 +2043,13 @@ class SetCheckedOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -1998,6 +2094,16 @@ public SetCheckedOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public SetCheckedOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -2070,6 +2176,13 @@ class TapOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -2124,6 +2237,16 @@ public TapOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public TapOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -2222,6 +2345,13 @@ class UncheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -2266,6 +2396,16 @@ public UncheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public UncheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout @@ -2333,6 +2473,26 @@ public WaitForOptions setTimeout(double timeout) { return this; } } + class WaitForFunctionOptions { + /** + * Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The + * default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout + * BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()} + * methods. + */ + public Double timeout; + + /** + * Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The + * default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout + * BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()} + * methods. + */ + public WaitForFunctionOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + } /** * When the locator points to a list of elements, this returns an array of locators, pointing to their respective elements. * @@ -5696,5 +5856,83 @@ default void waitFor() { * @since v1.16 */ void waitFor(WaitForOptions options); + /** + * Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code + * arg} as a second argument. + * + *

      This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is + * re-resolved on each retry, so it tolerates the element being re-rendered while waiting. + * + *

      If {@code expression} returns a Promise, this method + * will wait for the promise to resolve before checking its value. + * + *

      If {@code expression} throws or rejects, this method throws. + * + *

      Usage + * + *

      Wait for an attribute to appear: + * + *

      Passing argument to {@code expression}: + * + * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is + * automatically invoked. + * @param arg Optional argument to pass to {@code expression}. + * @since v1.62 + */ + default void waitForFunction(String expression, Object arg) { + waitForFunction(expression, arg, null); + } + /** + * Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code + * arg} as a second argument. + * + *

      This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is + * re-resolved on each retry, so it tolerates the element being re-rendered while waiting. + * + *

      If {@code expression} returns a Promise, this method + * will wait for the promise to resolve before checking its value. + * + *

      If {@code expression} throws or rejects, this method throws. + * + *

      Usage + * + *

      Wait for an attribute to appear: + * + *

      Passing argument to {@code expression}: + * + * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is + * automatically invoked. + * @since v1.62 + */ + default void waitForFunction(String expression) { + waitForFunction(expression, null); + } + /** + * Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code + * arg} as a second argument. + * + *

      This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is + * re-resolved on each retry, so it tolerates the element being re-rendered while waiting. + * + *

      If {@code expression} returns a Promise, this method + * will wait for the promise to resolve before checking its value. + * + *

      If {@code expression} throws or rejects, this method throws. + * + *

      Usage + * + *

      Wait for an attribute to appear: + * + *

      Passing argument to {@code expression}: + * + * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is + * automatically invoked. + * @param arg Optional argument to pass to {@code expression}. + * @since v1.62 + */ + void waitForFunction(String expression, Object arg, WaitForFunctionOptions options); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index db240171f..9d1295c43 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -435,6 +435,13 @@ class CheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -484,6 +491,16 @@ public CheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public CheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -545,6 +562,13 @@ class ClickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -625,6 +649,16 @@ public ClickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ClickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -710,6 +744,13 @@ class DblclickOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -783,6 +824,16 @@ public DblclickOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DblclickOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -855,6 +906,13 @@ class DragAndDropOptions { * @deprecated This option has no effect. */ public Boolean noWaitAfter; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -904,6 +962,16 @@ public DragAndDropOptions setNoWaitAfter(boolean noWaitAfter) { this.noWaitAfter = noWaitAfter; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public DragAndDropOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not * specified, some visible point of the element is used. @@ -1623,6 +1691,13 @@ class HoverOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -1682,6 +1757,16 @@ public HoverOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public HoverOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -2598,7 +2683,9 @@ class ScreenshotOptions { */ public Path path; /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public Integer quality; /** @@ -2707,7 +2794,9 @@ public ScreenshotOptions setPath(Path path) { return this; } /** - * The quality of the image, between 0-100. Not applicable to {@code png} images. + * The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code + * 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy + * compression. */ public ScreenshotOptions setQuality(int quality) { this.quality = quality; @@ -2823,6 +2912,13 @@ class SetCheckedOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -2872,6 +2968,16 @@ public SetCheckedOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public SetCheckedOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -3075,6 +3181,13 @@ class TapOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -3134,6 +3247,16 @@ public TapOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public TapOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -3266,6 +3389,13 @@ class UncheckOptions { * element. */ public Position position; + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public ScrollMode scroll; /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -3315,6 +3445,16 @@ public UncheckOptions setPosition(Position position) { this.position = position; return this; } + /** + * Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"}, + * which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code + * "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This + * is useful to assert that an element is reachable by the user without additional scrolling. + */ + public UncheckOptions setScroll(ScrollMode scroll) { + this.scroll = scroll; + return this; + } /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one * element, the call throws an exception. @@ -5464,6 +5604,11 @@ default Locator getByTitle(Pattern text) { * *

      Navigate to the previous page in history. * + *

      NOTE: **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache + * across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. + * Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code + * page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state. + * * @since v1.8 */ default Response goBack() { @@ -5475,6 +5620,11 @@ default Response goBack() { * *

      Navigate to the previous page in history. * + *

      NOTE: **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache + * across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. + * Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code + * page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state. + * * @since v1.8 */ Response goBack(GoBackOptions options); @@ -5484,6 +5634,11 @@ default Response goBack() { * *

      Navigate to the next page in history. * + *

      NOTE: **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache + * across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. + * Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code + * page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state. + * * @since v1.8 */ default Response goForward() { @@ -5495,6 +5650,11 @@ default Response goForward() { * *

      Navigate to the next page in history. * + *

      NOTE: **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache + * across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. + * Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code + * page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state. + * * @since v1.8 */ Response goForward(GoForwardOptions options); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java index 385aca2c8..21f96b2cb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java @@ -24,6 +24,7 @@ import com.microsoft.playwright.options.HttpHeader; import com.microsoft.playwright.options.SecurityDetails; import com.microsoft.playwright.options.ServerAddr; +import com.microsoft.playwright.options.Timing; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -118,6 +119,26 @@ public String text() { return new String(body(), StandardCharsets.UTF_8); } + @Override + public Timing timing() { + Timing timing; + if (initializer.has("timing")) { + timing = gson().fromJson(initializer.get("timing"), Timing.class); + } else { + timing = new Timing(); + timing.startTime = -1; + timing.domainLookupStart = -1; + timing.domainLookupEnd = -1; + timing.connectStart = -1; + timing.secureConnectionStart = -1; + timing.connectEnd = -1; + timing.requestStart = -1; + timing.responseStart = -1; + } + timing.responseEnd = initializer.has("responseEndTiming") ? initializer.get("responseEndTiming").getAsDouble() : -1; + return timing; + } + @Override public String url() { return initializer.get("url").getAsString(); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 78c63f897..500e9449a 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -547,6 +547,9 @@ public void routeFromHAR(Path har, RouteFromHAROptions options) { HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound); onClose(context -> harRouter.dispose()); route(matcher, route -> harRouter.handle(route), null); + if (options.interceptAPIRequests != null && options.interceptAPIRequests) { + harRouter.addAPIRequestRoute(this, options.url); + } } private void route(UrlMatcher matcher, Consumer handler, RouteOptions options) { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java index 729c300f3..a626bfc02 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ChannelOwner.java @@ -125,11 +125,12 @@ JsonElement sendMessage(String method) { JsonElement sendMessage(String method, JsonObject params, Double timeout) { checkNotCollected(); if (timeout != null) { - params.addProperty("timeout", timeout); + // Timeout is passed in the message metadata, remove potential leftover from serialized options. + params.remove("timeout"); } else if (params.has("timeout")) { throw new PlaywrightException("Internal error: timeout must be passed explicitly."); } - return connection.sendMessage(guid, method, params); + return connection.sendMessage(guid, method, params, timeout); } private void checkNotCollected() { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java index 46354abf2..b0bc2bd37 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java @@ -129,19 +129,23 @@ void close() throws IOException { } public JsonElement sendMessage(String guid, String method, JsonObject params) { - return root.runUntil(() -> {}, sendMessageAsync(guid, method, params)); + return sendMessage(guid, method, params, null); + } + + public JsonElement sendMessage(String guid, String method, JsonObject params, Double timeout) { + return root.runUntil(() -> {}, internalSendMessage(guid, method, params, timeout, true, true)); } public WaitableResult sendMessageAsync(String guid, String method, JsonObject params) { - return internalSendMessage(guid, method, params, true, true); + return internalSendMessage(guid, method, params, null, true, true); } // Fire-and-forget: the server never replies. public void sendMessageNoReply(String guid, String method, JsonObject params) { - internalSendMessage(guid, method, params, false, false); + internalSendMessage(guid, method, params, null, false, false); } - private WaitableResult internalSendMessage(String guid, String method, JsonObject params, boolean sendStack, boolean expectsReply) { + private WaitableResult internalSendMessage(String guid, String method, JsonObject params, Double timeout, boolean sendStack, boolean expectsReply) { int id = ++lastId; WaitableResult result = new WaitableResult<>(); if (expectsReply) { @@ -154,6 +158,9 @@ private WaitableResult internalSendMessage(String guid, String meth message.add("params", params); JsonObject metadata = new JsonObject(); metadata.addProperty("wallTime", currentTimeMillis()); + if (timeout != null) { + metadata.addProperty("timeout", timeout); + } JsonArray stack = null; if (titleReported) { metadata.addProperty("internal", true); @@ -183,7 +190,7 @@ private WaitableResult internalSendMessage(String guid, String meth callData.add("stack", stack); JsonObject stackParams = new JsonObject(); stackParams.add("callData", callData); - internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, false, true); + internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, null, false, true); } return result; } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java index a8b6667bc..a9e293896 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -38,6 +38,7 @@ import static com.microsoft.playwright.impl.Utils.addFilePathUploadParams; import static com.microsoft.playwright.options.ScreenshotType.JPEG; import static com.microsoft.playwright.options.ScreenshotType.PNG; +import static com.microsoft.playwright.options.ScreenshotType.WEBP; public class ElementHandleImpl extends JSHandleImpl implements ElementHandle { private final FrameImpl frame; @@ -278,6 +279,8 @@ public byte[] screenshot(ScreenshotOptions options) { String extension = fileName.substring(extStart).toLowerCase(); if (".jpeg".equals(extension) || ".jpg".equals(extension)) { options.type = JPEG; + } else if (".webp".equals(extension)) { + options.type = WEBP; } } } 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 c932cc99c..4ceeda0f1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/HARRouter.java @@ -25,18 +25,23 @@ import com.microsoft.playwright.options.HarNotFound; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT; import static com.microsoft.playwright.impl.LoggingSupport.*; import static com.microsoft.playwright.impl.Serialization.fromNameValues; import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Utils.toJsRegexFlags; public class HARRouter { private final LocalUtils localUtils; private final HarNotFound defaultAction; private final String harId; + private final List> apiRequestRegistrations = new ArrayList<>(); HARRouter(LocalUtils localUtils, Path harFile, HarNotFound defaultAction) { this.localUtils = localUtils; @@ -51,6 +56,21 @@ public class HARRouter { harId = json.get("harId").getAsString(); } + void addAPIRequestRoute(BrowserContextImpl context, Object url) { + JsonObject params = new JsonObject(); + params.addProperty("harId", harId); + if (url instanceof String) { + params.addProperty("urlGlob", (String) url); + } else if (url instanceof Pattern) { + Pattern pattern = (Pattern) url; + params.addProperty("urlRegexSource", pattern.pattern()); + params.addProperty("urlRegexFlags", toJsRegexFlags(pattern)); + } + params.addProperty("notFound", defaultAction == HarNotFound.FALLBACK ? "fallback" : "abort"); + JsonObject result = context.sendMessage("routeAPIRequestsFromHar", params, NO_TIMEOUT).getAsJsonObject(); + apiRequestRegistrations.add(new java.util.AbstractMap.SimpleEntry<>(context, result.get("registrationId").getAsString())); + } + void handle(Route route) { Request request = route.request(); @@ -127,6 +147,12 @@ private static Map mergeSetCookieHeaders(JsonArray headersArray) } void dispose() { + for (Map.Entry registration : apiRequestRegistrations) { + JsonObject unrouteParams = new JsonObject(); + unrouteParams.addProperty("registrationId", registration.getValue()); + registration.getKey().sendMessageAsync("unrouteAPIRequestsFromHar", unrouteParams); + } + apiRequestRegistrations.clear(); JsonObject params = new JsonObject(); params.addProperty("harId", harId); localUtils.sendMessageAsync("harClose", params); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java index ba71aa5ef..38dc949b9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java @@ -29,6 +29,7 @@ import static com.microsoft.playwright.impl.LocatorUtils.*; import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Serialization.serializeArgument; import static com.microsoft.playwright.impl.Utils.convertType; class LocatorImpl implements Locator { @@ -666,6 +667,16 @@ public void waitFor(WaitForOptions options) { frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true); } + @Override + public void waitForFunction(String expression, Object arg, WaitForFunctionOptions options) { + JsonObject params = new JsonObject(); + params.addProperty("selector", selector); + params.addProperty("strict", true); + params.addProperty("expression", expression); + params.add("arg", gson().toJsonTree(serializeArgument(arg))); + frame.sendMessage("waitForFunction", params, frame.timeout(options == null ? null : options.timeout)); + } + @Override public String toString() { String description = description(); 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 4956ea623..aca2c8370 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -35,6 +35,7 @@ import static com.microsoft.playwright.impl.Utils.*; import static com.microsoft.playwright.options.ScreenshotType.JPEG; import static com.microsoft.playwright.options.ScreenshotType.PNG; +import static com.microsoft.playwright.options.ScreenshotType.WEBP; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.Files.readAllBytes; import static java.util.Arrays.asList; @@ -1259,6 +1260,8 @@ private byte[] screenshotImpl(ScreenshotOptions options) { String extension = fileName.substring(extStart).toLowerCase(); if (".jpeg".equals(extension) || ".jpg".equals(extension)) { options.type = JPEG; + } else if (".webp".equals(extension)) { + options.type = WEBP; } } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ScreencastImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ScreencastImpl.java index a9d80aab0..aba7fcd91 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ScreencastImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ScreencastImpl.java @@ -39,15 +39,21 @@ class ScreencastImpl implements Screencast { } void handleScreencastFrame(JsonObject params) { - if (onFrame == null) { - return; + try { + if (onFrame != null) { + String dataBase64 = params.get("data").getAsString(); + byte[] data = java.util.Base64.getDecoder().decode(dataBase64); + double timestamp = params.get("timestamp").getAsDouble(); + int viewportWidth = params.get("viewportWidth").getAsInt(); + int viewportHeight = params.get("viewportHeight").getAsInt(); + onFrame.accept(new ScreencastFrameImpl(data, timestamp, viewportWidth, viewportHeight)); + } + } finally { + // The server sends the next frame only after the previous one is acknowledged. + JsonObject ackParams = new JsonObject(); + ackParams.add("frameId", params.get("frameId")); + page.sendMessageAsync("screencastFrameAck", ackParams); } - String dataBase64 = params.get("data").getAsString(); - byte[] data = java.util.Base64.getDecoder().decode(dataBase64); - double timestamp = params.get("timestamp").getAsDouble(); - int viewportWidth = params.get("viewportWidth").getAsInt(); - int viewportHeight = params.get("viewportHeight").getAsInt(); - onFrame.accept(new ScreencastFrameImpl(data, timestamp, viewportWidth, viewportHeight)); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index 34899dfbb..d07aaf569 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -58,6 +58,7 @@ class Serialization { .registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer()) .registerTypeAdapter(ServiceWorkerPolicy.class, new ToLowerCaseAndDashSerializer()) .registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer()) + .registerTypeAdapter(ScrollMode.class, new ToLowerCaseSerializer()) .registerTypeAdapter(ConsoleMessagesFilter.class, new ConsoleMessagesFilterSerializer()) .registerTypeAdapter(AriaSnapshotMode.class, new ToLowerCaseSerializer()) .registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer()) diff --git a/playwright/src/main/java/com/microsoft/playwright/options/ScreenshotType.java b/playwright/src/main/java/com/microsoft/playwright/options/ScreenshotType.java index 6c21e8d8b..6914e5dee 100644 --- a/playwright/src/main/java/com/microsoft/playwright/options/ScreenshotType.java +++ b/playwright/src/main/java/com/microsoft/playwright/options/ScreenshotType.java @@ -18,5 +18,6 @@ public enum ScreenshotType { PNG, - JPEG + JPEG, + WEBP } \ No newline at end of file diff --git a/playwright/src/main/java/com/microsoft/playwright/options/ScrollMode.java b/playwright/src/main/java/com/microsoft/playwright/options/ScrollMode.java new file mode 100644 index 000000000..170ed0e12 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/options/ScrollMode.java @@ -0,0 +1,22 @@ +/* + * 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 ScrollMode { + AUTO, + NONE +} \ No newline at end of file diff --git a/playwright/src/main/java/com/microsoft/playwright/options/Timing.java b/playwright/src/main/java/com/microsoft/playwright/options/Timing.java index 0415468d1..906539a64 100644 --- a/playwright/src/main/java/com/microsoft/playwright/options/Timing.java +++ b/playwright/src/main/java/com/microsoft/playwright/options/Timing.java @@ -22,42 +22,42 @@ public class Timing { */ public double startTime; /** - * Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds + * Time immediately before the client starts the domain name lookup for the resource. The value is given in milliseconds * relative to {@code startTime}, -1 if not available. */ public double domainLookupStart; /** - * Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds + * Time immediately after the client ends the domain name lookup for the resource. The value is given in milliseconds * relative to {@code startTime}, -1 if not available. */ public double domainLookupEnd; /** - * Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The - * value is given in milliseconds relative to {@code startTime}, -1 if not available. + * Time immediately before the client starts establishing the connection to the server to retrieve the resource. The value + * is given in milliseconds relative to {@code startTime}, -1 if not available. */ public double connectStart; /** - * Time immediately before the browser starts the handshake process to secure the current connection. The value is given in + * Time immediately before the client starts the handshake process to secure the current connection. The value is given in * milliseconds relative to {@code startTime}, -1 if not available. */ public double secureConnectionStart; /** - * Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The - * value is given in milliseconds relative to {@code startTime}, -1 if not available. + * Time immediately after the client establishes the connection to the server to retrieve the resource. The value is given + * in milliseconds relative to {@code startTime}, -1 if not available. */ public double connectEnd; /** - * Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value + * Time immediately before the client starts requesting the resource from the server, cache, or local resource. The value * is given in milliseconds relative to {@code startTime}, -1 if not available. */ public double requestStart; /** - * Time immediately after the browser receives the first byte of the response from the server, cache, or local resource. - * The value is given in milliseconds relative to {@code startTime}, -1 if not available. + * Time immediately after the client receives the first byte of the response from the server, cache, or local resource. The + * value is given in milliseconds relative to {@code startTime}, -1 if not available. */ public double responseStart; /** - * Time immediately after the browser receives the last byte of the resource or immediately before the transport connection + * Time immediately after the client receives the last byte of the resource or immediately before the transport connection * is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available. */ public double responseEnd; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java index a0783ac5b..0713f4b10 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java @@ -56,6 +56,22 @@ void getShouldWork() { assertEquals("{\"foo\": \"bar\"}\n", response.text()); } + @Test + void getShouldReturnTiming() { + APIResponse response = context.request().get(server.PREFIX + "/simple.json"); + assertTrue(response.ok()); + Timing timing = response.timing(); + assertTrue(timing.startTime > 0, "startTime = " + timing.startTime); + assertTrue(timing.domainLookupEnd >= timing.domainLookupStart); + assertTrue(timing.connectStart >= timing.domainLookupEnd); + assertEquals(-1, timing.secureConnectionStart); + assertTrue(timing.connectEnd >= timing.connectStart); + assertTrue(timing.requestStart >= timing.connectEnd); + assertTrue(timing.responseStart >= timing.requestStart); + assertTrue(timing.responseEnd >= timing.responseStart); + assertTrue(timing.responseEnd < 60_000, "responseEnd = " + timing.responseEnd); + } + @Test void fetchShouldWork() { APIResponse response = context.request().fetch(server.PREFIX + "/simple.json"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java index 9cc58d13c..bcc991dbe 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java @@ -19,6 +19,7 @@ import com.microsoft.playwright.options.HarMode; import com.microsoft.playwright.options.HarNotFound; import com.microsoft.playwright.options.RouteFromHarUpdateContentPolicy; +import com.microsoft.playwright.options.Timing; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.io.TempDir; @@ -501,4 +502,61 @@ void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) { assertNull(page.evaluate("window.result")); } } + + private void setJsonRoute(String path, String json) { + server.setRoute(path, exchange -> { + exchange.getResponseHeaders().add("Content-Type", "application/json"); + byte[] body = json.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, body.length); + try (OutputStream out = exchange.getResponseBody()) { + out.write(body); + } + }); + } + + @Test + void shouldFulfillAPIRequestContextRequestsFromHAR(@TempDir Path tmpDir) { + setJsonRoute("/api/data", "{\"hello\": \"live\"}"); + Path harPath = tmpDir.resolve("api.har"); + try (BrowserContext context1 = browser.newContext()) { + context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true)); + Page page1 = context1.newPage(); + page1.navigate(server.EMPTY_PAGE); + APIResponse recorded = page1.request().get(server.PREFIX + "/api/data"); + assertEquals("{\"hello\": \"live\"}", recorded.text()); + } + + // Now stop serving on the network side - the request must come from the HAR. + setJsonRoute("/api/data", "NOT_FROM_HAR"); + try (BrowserContext context2 = browser.newContext()) { + context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setInterceptAPIRequests(true)); + Page page2 = context2.newPage(); + APIResponse replayed = page2.request().get(server.PREFIX + "/api/data"); + assertEquals("{\"hello\": \"live\"}", replayed.text()); + Timing timing = replayed.timing(); + assertEquals(-1, timing.startTime); + assertEquals(-1, timing.responseEnd); + } + } + + @Test + void shouldNotInterceptAPIRequestContextRequestsByDefault(@TempDir Path tmpDir) { + setJsonRoute("/api/data", "{\"hello\": \"live\"}"); + Path harPath = tmpDir.resolve("api.har"); + try (BrowserContext context1 = browser.newContext()) { + context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true)); + Page page1 = context1.newPage(); + page1.navigate(server.EMPTY_PAGE); + page1.request().get(server.PREFIX + "/api/data"); + } + + // Without the option, the live network is hit. + setJsonRoute("/api/data", "{\"hello\": \"fresh\"}"); + try (BrowserContext context2 = browser.newContext()) { + context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK)); + Page page2 = context2.newPage(); + APIResponse replayed = page2.request().get(server.PREFIX + "/api/data"); + assertEquals("{\"hello\": \"fresh\"}", replayed.text()); + } + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorClick.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorClick.java index 7359ccc54..395e595b8 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorClick.java @@ -17,12 +17,13 @@ package com.microsoft.playwright; import com.microsoft.playwright.options.KeyboardModifier; +import com.microsoft.playwright.options.ScrollMode; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; @Tag("smoke") public class TestLocatorClick extends TestBase { @@ -104,4 +105,24 @@ void shouldClickWithTweenedMouseMovement() { page.evaluate("result") ); } + + @Test + void shouldNotScrollWhenScrollIsNone() { + page.setContent("

      filler
      \n" + + ""); + PlaywrightException e = assertThrows(PlaywrightException.class, + () -> page.locator("button").click(new Locator.ClickOptions().setScroll(ScrollMode.NONE).setTimeout(2000))); + assertTrue(e.getMessage().contains("element is outside of the viewport"), e.getMessage()); + assertNull(page.evaluate("window._clicked")); + assertEquals(0, page.evaluate("() => window.scrollY")); + } + + @Test + void shouldClickInViewportElementWhenScrollIsNone() { + page.setContent("\n" + + "
      "); + page.locator("button").click(new Locator.ClickOptions().setScroll(ScrollMode.NONE).setTimeout(2000)); + assertEquals(true, page.evaluate("window._clicked")); + assertEquals(0, page.evaluate("() => window.scrollY")); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java index 5e057ffdc..43cca0091 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java @@ -140,4 +140,49 @@ public void shouldSupportFilterVisible() { assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).getByText("data3")).hasText("visible data3"); assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(false)).getByText("data1")).hasText("Hidden data1"); } + + @Test + void waitForFunctionShouldWaitForAnAttributeToAppear() { + page.setContent(""); + page.evaluate("() => setTimeout(() => document.querySelector('#toggle').setAttribute('aria-expanded', 'true'), 500)"); + page.locator("#toggle").waitForFunction("element => element.hasAttribute('aria-expanded')"); + } + + @Test + void waitForFunctionShouldReturnImmediatelyWhenAlreadyTruthy() { + page.setContent("
      yes
      "); + page.locator("#target").waitForFunction("element => element.textContent === 'yes'"); + } + + @Test + void waitForFunctionShouldAcceptElementHandleArguments() { + page.setContent("
      value
      "); + ElementHandle handle = page.querySelector("#b"); + page.locator("#a").waitForFunction("(element, other) => other.textContent === 'value'", handle); + } + + @Test + void waitForFunctionShouldThrowWhenPredicateThrows() { + page.setContent("
      no
      "); + PlaywrightException e = assertThrows(PlaywrightException.class, + () -> page.locator("#target").waitForFunction("() => { throw new Error('oh my'); }")); + assertTrue(e.getMessage().contains("oh my"), e.getMessage()); + } + + @Test + void waitForFunctionShouldThrowOnStrictModeViolation() { + page.setContent("
      1
      2
      "); + PlaywrightException e = assertThrows(PlaywrightException.class, + () -> page.locator("div.x").waitForFunction("() => true")); + assertTrue(e.getMessage().contains("strict mode violation"), e.getMessage()); + } + + @Test + void waitForFunctionShouldRespectTimeout() { + page.setContent("
      no
      "); + PlaywrightException e = assertThrows(PlaywrightException.class, + () -> page.locator("#target").waitForFunction("element => element.textContent === 'yes'", null, + new Locator.WaitForFunctionOptions().setTimeout(500))); + assertTrue(e.getMessage().contains("Timeout 500ms exceeded"), e.getMessage()); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshotAI.java b/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshotAI.java index b29813211..ec5bc660e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshotAI.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageAriaSnapshotAI.java @@ -82,7 +82,10 @@ void shouldNotNestCursorPointerHints(Page page) { "Link with a button " + ""); String snapshot = aiSnapshot(page); - assertTrue(snapshot.contains("link \"Link with a button Button\" [ref=e2] [cursor=pointer]"), snapshot); + // The link's name is redundant - "Link with a button" prints as text and "Button" as the button - + // so it is dropped even though the node is clickable. + assertTrue(snapshot.contains("link [ref=e2] [cursor=pointer]"), snapshot); + assertTrue(snapshot.contains("text: Link with a button"), snapshot); // The button inside a cursor-pointer link should not get a redundant [cursor=pointer] assertTrue(snapshot.contains("button \"Button\" [ref=e3]"), snapshot); assertFalse(snapshot.contains("button \"Button\" [ref=e3] [cursor=pointer]"), snapshot); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java index d3485fc6f..9ba228b5e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java @@ -20,9 +20,11 @@ import com.microsoft.playwright.options.ScreenshotAnimations; import com.microsoft.playwright.options.ScreenshotCaret; import com.microsoft.playwright.options.ScreenshotScale; +import com.microsoft.playwright.options.ScreenshotType; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.io.TempDir; import org.opentest4j.AssertionFailedError; import javax.imageio.ImageIO; @@ -65,6 +67,39 @@ void shouldClipRect() throws IOException { // expect(screenshot).toMatchSnapshot("screenshot-clip-rect.png"); } + private static void assertWebp(byte[] screenshot) { + // WebP magic: "RIFF" at offset 0, "WEBP" at offset 8. + assertTrue(screenshot.length > 12); + assertEquals("RIFF", new String(screenshot, 0, 4, java.nio.charset.StandardCharsets.US_ASCII)); + assertEquals("WEBP", new String(screenshot, 8, 4, java.nio.charset.StandardCharsets.US_ASCII)); + } + + @Test + void shouldProduceAValidWebpScreenshot() { + page.setViewportSize(300, 300); + page.navigate(server.EMPTY_PAGE); + byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP)); + assertWebp(screenshot); + } + + @Test + void pathOptionShouldDetectWebp(@TempDir Path tmpDir) throws IOException { + page.setViewportSize(300, 300); + page.navigate(server.EMPTY_PAGE); + Path outputPath = tmpDir.resolve("screenshot.webp"); + byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setPath(outputPath)); + assertWebp(screenshot); + assertWebp(Files.readAllBytes(outputPath)); + } + + @Test + void qualityOptionShouldWorkForWebp() { + page.navigate(server.PREFIX + "/grid.html"); + byte[] lowQuality = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP).setQuality(0)); + byte[] highQuality = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP).setQuality(100)); + assertTrue(lowQuality.length < highQuality.length); + } + static private void rafraf(Page page) { // Do a double raf since single raf does not // actually guarantee a new animation frame. diff --git a/scripts/DRIVER_VERSION b/scripts/DRIVER_VERSION index 30e298c74..a77c2858a 100644 --- a/scripts/DRIVER_VERSION +++ b/scripts/DRIVER_VERSION @@ -1 +1 @@ -1.61.1 +1.62.0-alpha-2026-07-22 diff --git a/scripts/download_driver.sh b/scripts/download_driver.sh index c72dad687..32dfa0bb7 100755 --- a/scripts/download_driver.sh +++ b/scripts/download_driver.sh @@ -46,9 +46,15 @@ if [[ -z "$GIT_HEAD" ]]; then exit 1 fi -# The Node.js version is kept in sync with the driver version in the upstream build script. -NODE_VERSION=$(curl -fsSL "https://raw.githubusercontent.com/microsoft/playwright/$GIT_HEAD/utils/build/build-playwright-driver.sh" \ +# The Node.js version used to be pinned in the upstream driver build script. The script was +# removed in microsoft/playwright#41518, so for newer versions we follow the same policy it +# had: the latest Node.js LTS (see upstream utils/build/update-playwright-node.mjs). +NODE_VERSION=$(curl -fsSL "https://raw.githubusercontent.com/microsoft/playwright/$GIT_HEAD/utils/build/build-playwright-driver.sh" 2>/dev/null \ | sed -n 's/^NODE_VERSION="\([^"]*\)".*/\1/p') +if [[ -z "$NODE_VERSION" ]]; then + NODE_VERSION=$(curl -fsSL "https://nodejs.org/dist/index.json" \ + | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).find(r=>r.lts).version.slice(1)))") +fi if [[ -z "$NODE_VERSION" ]]; then echo "Failed to determine Node.js version for playwright@$DRIVER_VERSION ($GIT_HEAD)" exit 1