diff --git a/README.md b/README.md index 732973fb..d57ddbaf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 94.0.4595.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 95.0.4630.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 15.0 | ✅ | ✅ | ✅ | | Firefox 91.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index b895a262..1a5f44ab 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -89,6 +89,14 @@ public interface Browser extends AutoCloseable { * An object containing additional HTTP headers to be sent with every request. All header values must be strings. */ public Map extraHTTPHeaders; + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public ForcedColors forcedColors; public Geolocation geolocation; /** * Specifies if viewport supports touch events. Defaults to false. @@ -247,6 +255,17 @@ public interface Browser extends AutoCloseable { this.extraHTTPHeaders = extraHTTPHeaders; return this; } + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public NewContextOptions setForcedColors(ForcedColors forcedColors) { + this.forcedColors = forcedColors; + return this; + } public NewContextOptions setGeolocation(double latitude, double longitude) { return setGeolocation(new Geolocation(latitude, longitude)); } @@ -494,6 +513,14 @@ public interface Browser extends AutoCloseable { * An object containing additional HTTP headers to be sent with every request. All header values must be strings. */ public Map extraHTTPHeaders; + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public ForcedColors forcedColors; public Geolocation geolocation; /** * Specifies if viewport supports touch events. Defaults to false. @@ -652,6 +679,17 @@ public interface Browser extends AutoCloseable { this.extraHTTPHeaders = extraHTTPHeaders; return this; } + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public NewPageOptions setForcedColors(ForcedColors forcedColors) { + this.forcedColors = forcedColors; + return this; + } public NewPageOptions setGeolocation(double latitude, double longitude) { return setGeolocation(new Geolocation(latitude, longitude)); } diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index 36f422f6..e808ec13 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -160,6 +160,20 @@ public interface BrowserContext extends AutoCloseable { return this; } } + class RouteOptions { + /** + * How often a route should be used. By default it will be used every time. + */ + public Integer times; + + /** + * How often a route should be used. By default it will be used every time. + */ + public RouteOptions setTimes(int times) { + this.times = times; + return this; + } + } class StorageStateOptions { /** * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current @@ -537,6 +551,10 @@ public interface BrowserContext extends AutoCloseable { * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * BrowserContext context = browser.newContext();
@@ -578,11 +596,17 @@ public interface BrowserContext extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(String url, Consumer handler);
+  default void route(String url, Consumer handler) {
+    route(url, handler, null);
+  }
   /**
    * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
    * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
    *
+   * 

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * BrowserContext context = browser.newContext();
@@ -624,11 +648,15 @@ public interface BrowserContext extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(Pattern url, Consumer handler);
+  void route(String url, Consumer handler, RouteOptions options);
   /**
    * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
    * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
    *
+   * 

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * BrowserContext context = browser.newContext();
@@ -670,7 +698,161 @@ public interface BrowserContext extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(Predicate url, Consumer handler);
+  default void route(Pattern url, Consumer handler) {
+    route(url, handler, null);
+  }
+  /**
+   * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
+   * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
+   *
+   * 

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * context.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request + * matches both handlers. + * + *

To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + void route(Pattern url, Consumer handler, RouteOptions options); + /** + * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route + * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. + * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * context.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request + * matches both handlers. + * + *

To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + default void route(Predicate url, Consumer handler) { + route(url, handler, null); + } + /** + * Routing provides the capability to modify network requests that are made by any page in the browser context. Once route + * is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. + * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * BrowserContext context = browser.newContext();
+   * context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
+   * Page page = context.newPage();
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * context.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request + * matches both handlers. + * + *

To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + void route(Predicate url, Consumer handler, RouteOptions options); /** * This setting will change the default maximum navigation time for the following methods and related shortcuts: *

    diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index de852d70..83fb62f5 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -438,6 +438,14 @@ public interface BrowserType { * An object containing additional HTTP headers to be sent with every request. All header values must be strings. */ public Map extraHTTPHeaders; + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

    NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public ForcedColors forcedColors; public Geolocation geolocation; /** * Close the browser process on SIGHUP. Defaults to {@code true}. @@ -690,6 +698,17 @@ public interface BrowserType { this.extraHTTPHeaders = extraHTTPHeaders; return this; } + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia + * Page.emulateMedia()} for more details. Defaults to {@code "none"}. + * + *

    NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) { + this.forcedColors = forcedColors; + return this; + } public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) { return setGeolocation(new Geolocation(latitude, longitude)); } diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index bf0737e5..eef4a2ca 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -733,6 +733,87 @@ public interface ElementHandle extends JSHandle { return this; } } + class SetCheckedOptions { + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public Boolean force; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public Boolean noWaitAfter; + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public Position position; + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public Double timeout; + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public Boolean trial; + + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public SetCheckedOptions setForce(boolean force) { + this.force = force; + return this; + } + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) { + this.noWaitAfter = noWaitAfter; + return this; + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(double x, double y) { + return setPosition(new Position(x, y)); + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(Position position) { + this.position = position; + return this; + } + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public SetCheckedOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public SetCheckedOptions setTrial(boolean trial) { + this.trial = trial; + return this; + } + } class SetInputFilesOptions { /** * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can @@ -1016,6 +1097,11 @@ public interface ElementHandle extends JSHandle { *

*/ public WaitForSelectorState state; + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public Boolean strict; /** * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout @@ -1038,6 +1124,14 @@ public interface ElementHandle extends JSHandle { this.state = state; return this; } + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public WaitForSelectorOptions setStrict(boolean strict) { + this.strict = strict; + return this; + } /** * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout @@ -1922,6 +2016,46 @@ public interface ElementHandle extends JSHandle { * the element and selects all its text content. */ void selectText(SelectTextOptions options); + /** + * This method checks or unchecks an element by performing the following steps: + *
    + *
  1. Ensure that element is a checkbox or a radio input. If not, this method throws.
  2. + *
  3. If the element already has the right checked state, this method returns immediately.
  4. + *
  5. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  6. + *
  7. Scroll the element into view if needed.
  8. + *
  9. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  10. + *
  11. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  12. + *
  13. Ensure that the element is now checked or unchecked. If not, this method throws.
  14. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param checked Whether to check or uncheck the checkbox. + */ + default void setChecked(boolean checked) { + setChecked(checked, null); + } + /** + * This method checks or unchecks an element by performing the following steps: + *

    + *
  1. Ensure that element is a checkbox or a radio input. If not, this method throws.
  2. + *
  3. If the element already has the right checked state, this method returns immediately.
  4. + *
  5. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  6. + *
  7. Scroll the element into view if needed.
  8. + *
  9. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  10. + *
  11. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  12. + *
  13. Ensure that the element is now checked or unchecked. If not, this method throws.
  14. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param checked Whether to check or uncheck the checkbox. + */ + void setChecked(boolean checked, SetCheckedOptions options); /** * This method expects {@code elementHandle} to point to an input element. diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index 8b78e874..c3a7acc5 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -1164,9 +1164,8 @@ public interface Frame { */ public Boolean strict; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Frame#isHidden Frame.isHidden()} does not wait for the element to become + * hidden and returns immediately. */ public Double timeout; @@ -1179,9 +1178,8 @@ public interface Frame { return this; } /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Frame#isHidden Frame.isHidden()} does not wait for the element to become + * hidden and returns immediately. */ public IsHiddenOptions setTimeout(double timeout) { this.timeout = timeout; @@ -1195,9 +1193,8 @@ public interface Frame { */ public Boolean strict; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Frame#isVisible Frame.isVisible()} does not wait for the element to become + * visible and returns immediately. */ public Double timeout; @@ -1210,9 +1207,8 @@ public interface Frame { return this; } /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Frame#isVisible Frame.isVisible()} does not wait for the element to become + * visible and returns immediately. */ public IsVisibleOptions setTimeout(double timeout) { this.timeout = timeout; @@ -1351,6 +1347,100 @@ public interface Frame { return this; } } + class SetCheckedOptions { + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public Boolean force; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public Boolean noWaitAfter; + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public Position position; + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public Boolean strict; + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public Double timeout; + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public Boolean trial; + + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public SetCheckedOptions setForce(boolean force) { + this.force = force; + return this; + } + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) { + this.noWaitAfter = noWaitAfter; + return this; + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(double x, double y) { + return setPosition(new Position(x, y)); + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(Position position) { + this.position = position; + return this; + } + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public SetCheckedOptions setStrict(boolean strict) { + this.strict = strict; + return this; + } + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public SetCheckedOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public SetCheckedOptions setTrial(boolean trial) { + this.trial = trial; + return this; + } + } class SetContentOptions { /** * Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be @@ -3283,6 +3373,52 @@ public interface Frame { * is considered matching if all specified properties match. */ List selectOption(String selector, SelectOption[] values, SelectOptionOptions options); + /** + * This method checks or unchecks an element matching {@code selector} by performing the following steps: + *

    + *
  1. Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.
  2. + *
  3. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  4. + *
  5. If the element already has the right checked state, this method returns immediately.
  6. + *
  7. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  8. + *
  9. Scroll the element into view if needed.
  10. + *
  11. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  12. + *
  13. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  14. + *
  15. Ensure that the element is now checked or unchecked. If not, this method throws.
  16. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See + * working with selectors for more details. + * @param checked Whether to check or uncheck the checkbox. + */ + default void setChecked(String selector, boolean checked) { + setChecked(selector, checked, null); + } + /** + * This method checks or unchecks an element matching {@code selector} by performing the following steps: + *

    + *
  1. Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.
  2. + *
  3. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  4. + *
  5. If the element already has the right checked state, this method returns immediately.
  6. + *
  7. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  8. + *
  9. Scroll the element into view if needed.
  10. + *
  11. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  12. + *
  13. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  14. + *
  15. Ensure that the element is now checked or unchecked. If not, this method throws.
  16. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See + * working with selectors for more details. + * @param checked Whether to check or uncheck the checkbox. + */ + void setChecked(String selector, boolean checked, SetCheckedOptions options); /** * * diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java index 7db4c46a..8b15bc17 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Locator.java +++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java @@ -56,8 +56,11 @@ import java.util.*; * // Throws if there are several buttons in DOM: * page.locator("button").click(); * - * // Works because you explicitly tell locator to pick the first element: + * // Works because we explicitly tell locator to pick the first element: * page.locator("button").first().click(); + * + * // Works because count knows what to do with multiple matches: + * page.locator("button").count(); * }

*/ public interface Locator { @@ -764,16 +767,14 @@ public interface Locator { } class IsHiddenOptions { /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Locator#isHidden Locator.isHidden()} does not wait for the element to + * become hidden and returns immediately. */ public Double timeout; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Locator#isHidden Locator.isHidden()} does not wait for the element to + * become hidden and returns immediately. */ public IsHiddenOptions setTimeout(double timeout) { this.timeout = timeout; @@ -782,16 +783,14 @@ public interface Locator { } class IsVisibleOptions { /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Locator#isVisible Locator.isVisible()} does not wait for the element to + * become visible and returns immediately. */ public Double timeout; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Locator#isVisible Locator.isVisible()} does not wait for the element to + * become visible and returns immediately. */ public IsVisibleOptions setTimeout(double timeout) { this.timeout = timeout; @@ -1005,6 +1004,87 @@ public interface Locator { return this; } } + class SetCheckedOptions { + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public Boolean force; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public Boolean noWaitAfter; + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public Position position; + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public Double timeout; + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public Boolean trial; + + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public SetCheckedOptions setForce(boolean force) { + this.force = force; + return this; + } + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) { + this.noWaitAfter = noWaitAfter; + return this; + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(double x, double y) { + return setPosition(new Position(x, y)); + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(Position position) { + this.position = position; + return this; + } + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public SetCheckedOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public SetCheckedOptions setTrial(boolean trial) { + this.trial = trial; + return this; + } + } class SetInputFilesOptions { /** * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can @@ -2347,6 +2427,46 @@ public interface Locator { * the element and selects all its text content. */ void selectText(SelectTextOptions options); + /** + * This method checks or unchecks an element by performing the following steps: + *
    + *
  1. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  2. + *
  3. If the element already has the right checked state, this method returns immediately.
  4. + *
  5. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  6. + *
  7. Scroll the element into view if needed.
  8. + *
  9. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  10. + *
  11. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  12. + *
  13. Ensure that the element is now checked or unchecked. If not, this method throws.
  14. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param checked Whether to check or uncheck the checkbox. + */ + default void setChecked(boolean checked) { + setChecked(checked, null); + } + /** + * This method checks or unchecks an element by performing the following steps: + *

    + *
  1. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  2. + *
  3. If the element already has the right checked state, this method returns immediately.
  4. + *
  5. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  6. + *
  7. Scroll the element into view if needed.
  8. + *
  9. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  10. + *
  11. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  12. + *
  13. Ensure that the element is now checked or unchecked. If not, this method throws.
  14. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + * @param checked Whether to check or uncheck the checkbox. + */ + void setChecked(boolean checked, SetCheckedOptions options); /** * This method expects {@code element} to point to an input element. diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 570a2153..f14db33b 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -925,6 +925,14 @@ public interface Page extends AutoCloseable { * {@code null} disables color scheme emulation. */ public Optional colorScheme; + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code null} disables forced + * colors emulation. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public Optional forcedColors; /** * Changes the CSS media type of the page. The only allowed values are {@code "screen"}, {@code "print"} and {@code null}. Passing {@code null} * disables CSS media emulation. @@ -944,6 +952,17 @@ public interface Page extends AutoCloseable { this.colorScheme = Optional.ofNullable(colorScheme); return this; } + /** + * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code null} disables forced + * colors emulation. + * + *

NOTE: It's not supported in WebKit, see here in their issue + * tracker. + */ + public EmulateMediaOptions setForcedColors(ForcedColors forcedColors) { + this.forcedColors = Optional.ofNullable(forcedColors); + return this; + } /** * Changes the CSS media type of the page. The only allowed values are {@code "screen"}, {@code "print"} and {@code null}. Passing {@code null} * disables CSS media emulation. @@ -1566,9 +1585,8 @@ public interface Page extends AutoCloseable { */ public Boolean strict; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Page#isHidden Page.isHidden()} does not wait for the element to become + * hidden and returns immediately. */ public Double timeout; @@ -1581,9 +1599,8 @@ public interface Page extends AutoCloseable { return this; } /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Page#isHidden Page.isHidden()} does not wait for the element to become + * hidden and returns immediately. */ public IsHiddenOptions setTimeout(double timeout) { this.timeout = timeout; @@ -1597,9 +1614,8 @@ public interface Page extends AutoCloseable { */ public Boolean strict; /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Page#isVisible Page.isVisible()} does not wait for the element to become + * visible and returns immediately. */ public Double timeout; @@ -1612,9 +1628,8 @@ public interface Page extends AutoCloseable { return this; } /** - * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by - * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout - * Page.setDefaultTimeout()} methods. + * **DEPRECATED** This option is ignored. {@link Page#isVisible Page.isVisible()} does not wait for the element to become + * visible and returns immediately. */ public IsVisibleOptions setTimeout(double timeout) { this.timeout = timeout; @@ -1901,6 +1916,20 @@ public interface Page extends AutoCloseable { return this; } } + class RouteOptions { + /** + * How often a route should be used. By default it will be used every time. + */ + public Integer times; + + /** + * How often a route should be used. By default it will be used every time. + */ + public RouteOptions setTimes(int times) { + this.times = times; + return this; + } + } class ScreenshotOptions { /** * An object which specifies clipping of the resulting image. Should have the following fields: @@ -2058,6 +2087,100 @@ public interface Page extends AutoCloseable { return this; } } + class SetCheckedOptions { + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public Boolean force; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public Boolean noWaitAfter; + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public Position position; + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public Boolean strict; + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public Double timeout; + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public Boolean trial; + + /** + * Whether to bypass the actionability checks. Defaults to + * {@code false}. + */ + public SetCheckedOptions setForce(boolean force) { + this.force = force; + return this; + } + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to {@code false}. + */ + public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) { + this.noWaitAfter = noWaitAfter; + return this; + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(double x, double y) { + return setPosition(new Position(x, y)); + } + /** + * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the + * element. + */ + public SetCheckedOptions setPosition(Position position) { + this.position = position; + return this; + } + /** + * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one + * element, the call throws an exception. + */ + public SetCheckedOptions setStrict(boolean strict) { + this.strict = strict; + return this; + } + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by + * using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout + * Page.setDefaultTimeout()} methods. + */ + public SetCheckedOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + /** + * When set, this method only performs the actionability + * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without + * performing it. + */ + public SetCheckedOptions setTrial(boolean trial) { + this.trial = trial; + return this; + } + } class SetContentOptions { /** * Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be @@ -4429,6 +4552,10 @@ public interface Page extends AutoCloseable { * *

NOTE: The handler will only be called for the first url if the response is a redirect. * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * Page page = browser.newPage();
@@ -4468,7 +4595,9 @@ public interface Page extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(String url, Consumer handler);
+  default void route(String url, Consumer handler) {
+    route(url, handler, null);
+  }
   /**
    * Routing provides the capability to modify network requests that are made by a page.
    *
@@ -4476,6 +4605,10 @@ public interface Page extends AutoCloseable {
    *
    * 

NOTE: The handler will only be called for the first url if the response is a redirect. * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * Page page = browser.newPage();
@@ -4515,7 +4648,7 @@ public interface Page extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(Pattern url, Consumer handler);
+  void route(String url, Consumer handler, RouteOptions options);
   /**
    * Routing provides the capability to modify network requests that are made by a page.
    *
@@ -4523,6 +4656,10 @@ public interface Page extends AutoCloseable {
    *
    * 

NOTE: The handler will only be called for the first url if the response is a redirect. * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * *

An example of a naive handler that aborts all image requests: *

{@code
    * Page page = browser.newPage();
@@ -4562,7 +4699,164 @@ public interface Page extends AutoCloseable {
    * href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()} constructor.
    * @param handler handler function to route the request.
    */
-  void route(Predicate url, Consumer handler);
+  default void route(Pattern url, Consumer handler) {
+    route(url, handler, null);
+  }
+  /**
+   * Routing provides the capability to modify network requests that are made by a page.
+   *
+   * 

Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. + * + *

NOTE: The handler will only be called for the first url if the response is a redirect. + * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * Page page = browser.newPage();
+   * page.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * Page page = browser.newPage();
+   * page.route(Pattern.compile("(\\.png$)|(\\.jpg$)"),route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * page.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes take precedence over browser context routes (set up with {@link BrowserContext#route + * BrowserContext.route()}) when request matches both handlers. + * + *

To remove a route with its handler you can use {@link Page#unroute Page.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + void route(Pattern url, Consumer handler, RouteOptions options); + /** + * Routing provides the capability to modify network requests that are made by a page. + * + *

Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. + * + *

NOTE: The handler will only be called for the first url if the response is a redirect. + * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * Page page = browser.newPage();
+   * page.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * Page page = browser.newPage();
+   * page.route(Pattern.compile("(\\.png$)|(\\.jpg$)"),route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * page.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes take precedence over browser context routes (set up with {@link BrowserContext#route + * BrowserContext.route()}) when request matches both handlers. + * + *

To remove a route with its handler you can use {@link Page#unroute Page.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + default void route(Predicate url, Consumer handler) { + route(url, handler, null); + } + /** + * Routing provides the capability to modify network requests that are made by a page. + * + *

Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. + * + *

NOTE: The handler will only be called for the first url if the response is a redirect. + * + *

NOTE: {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See this issue. We recommend disabling Service Workers when + * using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);} + * + *

An example of a naive handler that aborts all image requests: + *

{@code
+   * Page page = browser.newPage();
+   * page.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

or the same snippet using a regex pattern instead: + *

{@code
+   * Page page = browser.newPage();
+   * page.route(Pattern.compile("(\\.png$)|(\\.jpg$)"),route -> route.abort());
+   * page.navigate("https://example.com");
+   * browser.close();
+   * }
+ * + *

It is possible to examine the request to decide the route action. For example, mocking all requests that contain some + * post data, and leaving all other requests as is: + *

{@code
+   * page.route("/api/**", route -> {
+   *   if (route.request().postData().contains("my-string"))
+   *     route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
+   *   else
+   *     route.resume();
+   * });
+   * }
+ * + *

Page routes take precedence over browser context routes (set up with {@link BrowserContext#route + * BrowserContext.route()}) when request matches both handlers. + * + *

To remove a route with its handler you can use {@link Page#unroute Page.unroute()}. + * + *

NOTE: Enabling routing disables http cache. + * + * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context + * options was provided and the passed URL is a path, it gets merged via the {@code new URL()} constructor. + * @param handler handler function to route the request. + */ + void route(Predicate url, Consumer handler, RouteOptions options); /** * Returns the buffer with the captured screenshot. */ @@ -4957,6 +5251,56 @@ public interface Page extends AutoCloseable { * is considered matching if all specified properties match. */ List selectOption(String selector, SelectOption[] values, SelectOptionOptions options); + /** + * This method checks or unchecks an element matching {@code selector} by performing the following steps: + *

    + *
  1. Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.
  2. + *
  3. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  4. + *
  5. If the element already has the right checked state, this method returns immediately.
  6. + *
  7. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  8. + *
  9. Scroll the element into view if needed.
  10. + *
  11. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  12. + *
  13. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  14. + *
  15. Ensure that the element is now checked or unchecked. If not, this method throws.
  16. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + *

Shortcut for main frame's {@link Frame#setChecked Frame.setChecked()}. + * + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See + * working with selectors for more details. + * @param checked Whether to check or uncheck the checkbox. + */ + default void setChecked(String selector, boolean checked) { + setChecked(selector, checked, null); + } + /** + * This method checks or unchecks an element matching {@code selector} by performing the following steps: + *

    + *
  1. Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.
  2. + *
  3. Ensure that matched element is a checkbox or a radio input. If not, this method throws.
  4. + *
  5. If the element already has the right checked state, this method returns immediately.
  6. + *
  7. Wait for actionability checks on the matched element, + * unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.
  8. + *
  9. Scroll the element into view if needed.
  10. + *
  11. Use {@link Page#mouse Page.mouse()} to click in the center of the element.
  12. + *
  13. Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.
  14. + *
  15. Ensure that the element is now checked or unchecked. If not, this method throws.
  16. + *
+ * + *

When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing + * zero timeout disables this. + * + *

Shortcut for main frame's {@link Frame#setChecked Frame.setChecked()}. + * + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See + * working with selectors for more details. + * @param checked Whether to check or uncheck the checkbox. + */ + void setChecked(String selector, boolean checked, SetCheckedOptions options); /** * * diff --git a/playwright/src/main/java/com/microsoft/playwright/Request.java b/playwright/src/main/java/com/microsoft/playwright/Request.java index 014b1960..224b6ffe 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Request.java +++ b/playwright/src/main/java/com/microsoft/playwright/Request.java @@ -38,6 +38,10 @@ import java.util.*; * request is issued to a redirected url. */ public interface Request { + /** + * An object with all the request HTTP headers associated with this request. The header names are lower-cased. + */ + Map allHeaders(); /** * The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event. * @@ -54,9 +58,16 @@ public interface Request { */ Frame frame(); /** - * An object with HTTP headers associated with the request. All header names are lower-case. + * **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders + * Request.allHeaders()} instead. */ Map headers(); + /** + * An array with all the request HTTP headers associated with this request. Unlike {@link Request#allHeaders + * Request.allHeaders()}, header names are not lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in + * the array multiple times. + */ + List headersArray(); /** * Whether this request is driving frame's navigation. */ @@ -112,6 +123,10 @@ public interface Request { * Returns the matching {@code Response} object, or {@code null} if the response was not received due to error. */ Response response(); + /** + * Returns resource size information for given request. + */ + Sizes sizes(); /** * Returns resource timing information for given request. Most of the timing values become available upon the response, * {@code responseEnd} becomes available when request finishes. Find more information at allHeaders(); /** * Returns the buffer with response body. */ @@ -36,9 +40,16 @@ public interface Response { */ Frame frame(); /** - * Returns the object with HTTP headers associated with the response. All header names are lower-case. + * **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders + * Response.allHeaders()} instead. */ Map headers(); + /** + * An array with all the request HTTP headers associated with this response. Unlike {@link Response#allHeaders + * Response.allHeaders()}, header names are not lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in + * the array multiple times. + */ + List headersArray(); /** * Contains a boolean stating whether the response was successful (status in the range 200-299) or not. */ diff --git a/playwright/src/main/java/com/microsoft/playwright/Tracing.java b/playwright/src/main/java/com/microsoft/playwright/Tracing.java index 10d359fa..f6152c17 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Tracing.java +++ b/playwright/src/main/java/com/microsoft/playwright/Tracing.java @@ -20,10 +20,10 @@ import java.nio.file.Path; import java.util.*; /** - * API for collecting and saving Playwright traces. Playwright traces can be opened using the Playwright CLI after - * Playwright script runs. + * API for collecting and saving Playwright traces. Playwright traces can be opened in Trace Viewer after Playwright script runs. * - *

Start with specifying the folder traces will be stored in: + *

Start recording a trace before performing actions. At the end, stop tracing and save it to a file. *

{@code
  * Browser browser = chromium.launch();
  * BrowserContext context = browser.newContext();
@@ -77,18 +77,34 @@ public interface Tracing {
   }
   class StopOptions {
     /**
-     * Export trace into the file with the given name.
+     * Export trace into the file with the given path.
      */
     public Path path;
 
     /**
-     * Export trace into the file with the given name.
+     * Export trace into the file with the given path.
      */
     public StopOptions setPath(Path path) {
       this.path = path;
       return this;
     }
   }
+  class StopChunkOptions {
+    /**
+     * Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
+     * path.
+     */
+    public Path path;
+
+    /**
+     * Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
+     * path.
+     */
+    public StopChunkOptions setPath(Path path) {
+      this.path = path;
+      return this;
+    }
+  }
   /**
    * Start tracing.
    * 
{@code
@@ -117,6 +133,31 @@ public interface Tracing {
    * }
*/ void start(StartOptions options); + /** + * Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start + * Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and + * {@link Tracing#stopChunk Tracing.stopChunk()}. + *
{@code
+   * context.tracing().start(new Tracing.StartOptions()
+   *   .setScreenshots(true)
+   *   .setSnapshots(true));
+   * Page page = context.newPage();
+   * page.navigate("https://playwright.dev");
+   *
+   * context.tracing().startChunk();
+   * page.click("text=Get Started");
+   * // Everything between startChunk and stopChunk will be recorded in the trace.
+   * context.tracing().stopChunk(new Tracing.StopChunkOptions()
+   *   .setPath(Paths.get("trace1.zip")));
+   *
+   * context.tracing().startChunk();
+   * page.navigate("http://example.com");
+   * // Save a second trace file with different actions.
+   * context.tracing().stopChunk(new Tracing.StopChunkOptions()
+   *   .setPath(Paths.get("trace2.zip")));
+   * }
+ */ + void startChunk(); /** * Stop tracing. */ @@ -127,5 +168,15 @@ public interface Tracing { * Stop tracing. */ void stop(StopOptions options); + /** + * Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks. + */ + default void stopChunk() { + stopChunk(null); + } + /** + * Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks. + */ + void stopChunk(StopChunkOptions options); } 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 f37cfae9..c91f0e8c 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -56,6 +56,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { final TimeoutSettings timeoutSettings = new TimeoutSettings(); Path videosDir; URL baseUrl; + Path recordHarPath; enum EventType { CLOSE, @@ -180,6 +181,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } isClosedOrClosing = true; try { + if (recordHarPath != null) { + JsonObject json = sendMessage("harExport").getAsJsonObject(); + ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString()); + // In case of CDP connection browser is null but since the connection is established by + // the driver it is safe to consider the artifact local. + if (browser() != null && browser().isRemote) { + artifact.isRemote = true; + } + artifact.saveAs(recordHarPath); + artifact.delete(); + } + sendMessage("close"); } catch (PlaywrightException e) { if (!isSafeCloseError(e)) { @@ -317,23 +330,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } @Override - public void route(String url, Consumer handler) { - route(new UrlMatcher(this.baseUrl, url), handler); + public void route(String url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(this.baseUrl, url), handler, options); } @Override - public void route(Pattern url, Consumer handler) { - route(new UrlMatcher(url), handler); + public void route(Pattern url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(url), handler, options); } @Override - public void route(Predicate url, Consumer handler) { - route(new UrlMatcher(url), handler); + public void route(Predicate url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(url), handler, options); } - private void route(UrlMatcher matcher, Consumer handler) { + private void route(UrlMatcher matcher, Consumer handler, RouteOptions options) { withLogging("BrowserContext.route", () -> { - routes.add(matcher, handler); + routes.add(matcher, handler, options == null ? null : options.times); if (routes.size() == 1) { JsonObject params = new JsonObject(); params.addProperty("enabled", true); @@ -488,6 +501,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } else if ("requestFailed".equals(event)) { String guid = params.getAsJsonObject("request").get("guid").getAsString(); RequestImpl request = connection.getExistingObject(guid); + request.didFailOrFinish = true; if (params.has("failureText")) { request.failure = params.get("failureText").getAsString(); } @@ -502,6 +516,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } else if ("requestFinished".equals(event)) { String guid = params.getAsJsonObject("request").get("guid").getAsString(); RequestImpl request = connection.getExistingObject(guid); + request.didFailOrFinish = true; if (request.timing != null) { request.timing.responseEnd = params.get("responseEndTiming").getAsDouble(); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java index 5d9c5b72..d5da0ce1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java @@ -167,12 +167,11 @@ class BrowserImpl extends ChannelOwner implements Browser { } JsonElement result = sendMessage("newContext", params); BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString()); - if (options.recordVideoDir != null) { - context.videosDir = options.recordVideoDir; - } + context.videosDir = options.recordVideoDir; if (options.baseURL != null) { context.setBaseUrl(options.baseURL); } + context.recordHarPath = options.recordHarPath; contexts.add(context); return context; } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java index 0f0e8f43..47075226 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java @@ -184,12 +184,11 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType { } JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject(); BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString()); - if (options.recordVideoDir != null) { - context.videosDir = options.recordVideoDir; - } + context.videosDir = options.recordVideoDir; if (options.baseURL != null) { context.setBaseUrl(options.baseURL); } + context.recordHarPath = options.recordHarPath; return context; } 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 003c4f97..b518cc7f 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -34,6 +34,7 @@ import java.util.Base64; import java.util.List; import static com.microsoft.playwright.impl.Serialization.*; +import static com.microsoft.playwright.impl.Utils.convertViaJson; import static com.microsoft.playwright.options.ScreenshotType.JPEG; import static com.microsoft.playwright.options.ScreenshotType.PNG; @@ -431,6 +432,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle { withLogging("ElementHandle.selectText", () -> selectTextImpl(options)); } + @Override + public void setChecked(boolean checked, SetCheckedOptions options) { + if (checked) { + check(convertViaJson(options, CheckOptions.class)); + } else { + uncheck(convertViaJson(options, UncheckOptions.class)); + } + } + @Override public void setInputFiles(Path files, SetInputFilesOptions options) { setInputFiles(new Path[]{files}, options); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index a9035936..08889cd0 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -643,6 +643,19 @@ public class FrameImpl extends ChannelOwner implements Frame { return parseStringList(json.getAsJsonArray("values")); } + @Override + public void setChecked(String selector, boolean checked, SetCheckedOptions options) { + withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options)); + } + + void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) { + if (checked) { + checkImpl(selector, convertViaJson(options, CheckOptions.class)); + } else { + uncheckImpl(selector, convertViaJson(options, UncheckOptions.class)); + } + } + @Override public void setContent(String html, SetContentOptions options) { withLogging("Frame.setContent", () -> setContentImpl(html, options)); 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 1e30bbbc..842824a4 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java @@ -327,6 +327,14 @@ class LocatorImpl implements Locator { }, convertViaJson(options, ElementHandle.SelectTextOptions.class)); } + @Override + public void setChecked(boolean checked, SetCheckedOptions options) { + if (options == null) { + options = new SetCheckedOptions(); + } + frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class).setStrict(true)); + } + @Override public void setInputFiles(Path files, SetInputFilesOptions options) { if (options == null) { 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 20663a86..ee8c67d8 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -943,23 +943,23 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public void route(String url, Consumer handler) { - route(new UrlMatcher(browserContext.baseUrl, url), handler); + public void route(String url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(browserContext.baseUrl, url), handler, options); } @Override - public void route(Pattern url, Consumer handler) { - route(new UrlMatcher(url), handler); + public void route(Pattern url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(url), handler, options); } @Override - public void route(Predicate url, Consumer handler) { - route(new UrlMatcher(url), handler); + public void route(Predicate url, Consumer handler, RouteOptions options) { + route(new UrlMatcher(url), handler, options); } - private void route(UrlMatcher matcher, Consumer handler) { + private void route(UrlMatcher matcher, Consumer handler, RouteOptions options) { withLogging("Page.route", () -> { - routes.add(matcher, handler); + routes.add(matcher, handler, options == null ? null : options.times); if (routes.size() == 1) { JsonObject params = new JsonObject(); params.addProperty("enabled", true); @@ -1042,6 +1042,12 @@ public class PageImpl extends ChannelOwner implements Page { () -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class))); } + @Override + public void setChecked(String selector, boolean checked, SetCheckedOptions options) { + withLogging("Page.setChecked", + () -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class))); + } + @Override public void setContent(String html, SetContentOptions options) { withLogging("Page.setContent", diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java index 86022f57..4d144b30 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/RequestImpl.java @@ -16,25 +16,32 @@ package com.microsoft.playwright.impl; -import com.google.gson.JsonElement; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.microsoft.playwright.Frame; +import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.Request; -import com.microsoft.playwright.Response; +import com.microsoft.playwright.options.HttpHeader; +import com.microsoft.playwright.options.Sizes; import com.microsoft.playwright.options.Timing; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Utils.toHeadersMap; +import static java.util.Arrays.asList; + public class RequestImpl extends ChannelOwner implements Request { private final byte[] postData; private RequestImpl redirectedFrom; private RequestImpl redirectedTo; - final Map headers = new HashMap<>(); + private final List headers; + private List rawHeaders; String failure; Timing timing; + boolean didFailOrFinish; RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -43,10 +50,7 @@ public class RequestImpl extends ChannelOwner implements Request { redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString()); redirectedFrom.redirectedTo = this; } - for (JsonElement e : initializer.getAsJsonArray("headers")) { - JsonObject item = e.getAsJsonObject(); - headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString()); - } + headers = asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)); if (initializer.has("postData")) { postData = Base64.getDecoder().decode(initializer.get("postData").getAsString()); } else { @@ -54,19 +58,29 @@ public class RequestImpl extends ChannelOwner implements Request { } } + @Override + public Map allHeaders() { + return withLogging("Request.allHeaders", () -> toHeadersMap(getRawHeaders())); + } + @Override public String failure() { return failure; } @Override - public Frame frame() { + public FrameImpl frame() { return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString()); } @Override public Map headers() { - return headers; + return toHeadersMap(headers); + } + + @Override + public List headersArray() { + return withLogging("Request.headersArray", () -> getRawHeaders()); } @Override @@ -108,7 +122,7 @@ public class RequestImpl extends ChannelOwner implements Request { } @Override - public Response response() { + public ResponseImpl response() { return withLogging("Request.response", () -> { JsonObject result = sendMessage("response").getAsJsonObject(); if (!result.has("response")) { @@ -118,6 +132,18 @@ public class RequestImpl extends ChannelOwner implements Request { }); } + @Override + public Sizes sizes() { + return withLogging("Request.sizes", () -> { + ResponseImpl response = response(); + if (response == null) { + throw new PlaywrightException("Unable to fetch sizes for failed request"); + } + JsonObject json = response.sendMessage("sizes").getAsJsonObject(); + return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class); + }); + } + @Override public Timing timing() { return timing; @@ -132,4 +158,22 @@ public class RequestImpl extends ChannelOwner implements Request { return redirectedTo != null ? redirectedTo.finalRequest() : this; } + private List getRawHeaders() { + if (rawHeaders != null) { + return rawHeaders; + } + ResponseImpl response = response(); + // there is no response, so should we return the headers we have now? + if (response == null) { + return headers; + } + JsonArray rawHeadersJson = response.withLogging("Request.allHeaders", () -> { + JsonObject result = response.sendMessage("rawRequestHeaders").getAsJsonObject(); + return result.getAsJsonArray("headers"); + }); + + // The field may have been initialized in a nested call but it is ok. + rawHeaders = asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)); + return rawHeaders; + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java index 0c5a6e1a..b2782036 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ResponseImpl.java @@ -19,21 +19,22 @@ package com.microsoft.playwright.impl; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.Frame; -import com.microsoft.playwright.Request; import com.microsoft.playwright.Response; +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; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Utils.toHeadersMap; +import static java.util.Arrays.asList; public class ResponseImpl extends ChannelOwner implements Response { private final Map headers = new HashMap<>(); + private List rawHeaders; private final RequestImpl request; ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { @@ -45,14 +46,14 @@ public class ResponseImpl extends ChannelOwner implements Response { } request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString()); - request.headers.clear(); - for (JsonElement e : initializer.getAsJsonArray("requestHeaders")) { - JsonObject item = e.getAsJsonObject(); - request.headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString()); - } request.timing = gson().fromJson(initializer.get("timing"), Timing.class); } + @Override + public Map allHeaders() { + return withLogging("Response.allHeaders", () -> toHeadersMap(getRawHeaders())); + } + @Override public byte[] body() { return withLogging("Response.body", () -> { @@ -63,13 +64,22 @@ public class ResponseImpl extends ChannelOwner implements Response { @Override public String finished() { - return withLogging("Response.finished", () -> { - JsonObject json = sendMessage("finished").getAsJsonObject(); - if (json.has("error")) { - return json.get("error").getAsString(); + List> waitables = new ArrayList<>(); + waitables.add(new WaitableNever() { + @Override + public boolean isDone() { + return request.didFailOrFinish; + } + @Override + public String get() { + return request.failure(); } - return null; }); + PageImpl page = request.frame().page; + waitables.add(page.createWaitForCloseHelper()); + waitables.add(page.createWaitableTimeout(null)); + runUntil(() -> {}, new WaitableRace<>(waitables)); + return request.failure(); } @Override @@ -82,6 +92,11 @@ public class ResponseImpl extends ChannelOwner implements Response { return headers; } + @Override + public List headersArray() { + return withLogging("Response.headersArray", () -> getRawHeaders()); + } + @Override public boolean ok() { return status() == 0 || (status() >= 200 && status() <= 299); @@ -133,4 +148,12 @@ public class ResponseImpl extends ChannelOwner implements Response { public String url() { return initializer.get("url").getAsString(); } + + private List getRawHeaders() { + if (rawHeaders == null) { + JsonObject json = sendMessage("rawResponseHeaders").getAsJsonObject(); + rawHeaders = asList(gson().fromJson(json.getAsJsonArray("headers"), HttpHeader[].class)); + } + return rawHeaders; + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Router.java b/playwright/src/main/java/com/microsoft/playwright/impl/Router.java index 0c096ec6..bf0b4a0e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Router.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Router.java @@ -29,15 +29,31 @@ class Router { private static class RouteInfo { final UrlMatcher matcher; final Consumer handler; + Integer times; - RouteInfo(UrlMatcher matcher, Consumer handler) { + RouteInfo(UrlMatcher matcher, Consumer handler, Integer times) { this.matcher = matcher; this.handler = handler; + this.times = times; + } + + boolean handle(Route route) { + if (times != null && times <= 0) { + return false; + } + if (!matcher.test(route.request().url())) { + return false; + } + if (times != null) { + --times; + } + handler.accept(route); + return true; } } - void add(UrlMatcher matcher, Consumer handler) { - routes.add(0, new RouteInfo(matcher, handler)); + void add(UrlMatcher matcher, Consumer handler, Integer times) { + routes.add(0, new RouteInfo(matcher, handler, times)); } void remove(UrlMatcher matcher, Consumer handler) { @@ -52,8 +68,7 @@ class Router { boolean handle(Route route) { for (RouteInfo info : routes) { - if (info.matcher.test(route.request().url())) { - info.handler.accept(route); + if (info.handle(route)) { return true; } } 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 1c3a77bd..d89aebc7 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -39,9 +39,11 @@ class Serialization { if (gson == null) { gson = new GsonBuilder() .registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe()) - .registerTypeAdapter(BrowserChannel.class, new BrowserChannelSerializer()) - .registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe()) - .registerTypeAdapter(Media.class, new MediaSerializer()) + .registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer()) + .registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer()) + .registerTypeAdapter(Media.class, new ToLowerCaseSerializer()) + .registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer()) + .registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer()) .registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer()) .registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer()) .registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer()) @@ -255,6 +257,8 @@ class Serialization { private static boolean isSupported(Type type) { return new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || + new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || + new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()); } @@ -303,11 +307,10 @@ class Serialization { return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-')); } } - - private static class MediaSerializer implements JsonSerializer { + private static class ToLowerCaseAndDashSerializer> implements JsonSerializer { @Override - public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.toString().toLowerCase()); + public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-')); } } @@ -351,37 +354,5 @@ class Serialization { return SameSiteAttribute.valueOf(value.toUpperCase()); } } - - private static class ColorSchemeAdapter extends TypeAdapter { - @Override - public void write(JsonWriter out, ColorScheme value) throws IOException { - String stringValue; - switch (value) { - case DARK: - stringValue = "dark"; - break; - case LIGHT: - stringValue = "light"; - break; - case NO_PREFERENCE: - stringValue = "no-preference"; - break; - default: - throw new PlaywrightException("Unexpected value: " + value); - } - out.value(stringValue); - } - - @Override - public ColorScheme read(JsonReader in) throws IOException { - String value = in.nextString(); - switch (value) { - case "dark": return ColorScheme.DARK; - case "light": return ColorScheme.LIGHT; - case "no-preference": return ColorScheme.NO_PREFERENCE; - default: throw new PlaywrightException("Unexpected value: " + value); - } - } - } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java index 308cd438..446836fe 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java @@ -31,8 +31,13 @@ class TracingImpl implements Tracing { this.context = context; } - private void export(Path path) { - JsonObject json = context.sendMessage("tracingExport").getAsJsonObject(); + private void stopChunkImpl(Path path) { + JsonObject params = new JsonObject(); + params.addProperty("save", path != null); + JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject(); + if (!json.has("artifact")) { + return; + } ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString()); // In case of CDP connection browser is null but since the connection is established by // the driver it is safe to consider the artifact local. @@ -48,21 +53,34 @@ class TracingImpl implements Tracing { context.withLogging("Tracing.start", () -> startImpl(options)); } + @Override + public void startChunk() { + context.withLogging("Tracing.startChunk", () -> { + context.sendMessage("tracingStartChunk"); + }); + } + private void startImpl(StartOptions options) { if (options == null) { options = new StartOptions(); } JsonObject params = gson().toJsonTree(options).getAsJsonObject(); context.sendMessage("tracingStart", params); + context.sendMessage("tracingStartChunk"); } @Override public void stop(StopOptions options) { context.withLogging("Tracing.stop", () -> { - if (options != null && options.path != null) { - export(options.path); - } + stopChunkImpl(options == null ? null : options.path); context.sendMessage("tracingStop"); }); } + + @Override + public void stopChunk(StopChunkOptions options) { + context.withLogging("Tracing.stopChunk", () -> { + stopChunkImpl(options == null ? null : options.path); + }); + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java index 78ef3e73..ddd0ae5e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Utils.java @@ -19,6 +19,7 @@ package com.microsoft.playwright.impl; import com.google.gson.*; import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.options.FilePayload; +import com.microsoft.playwright.options.HttpHeader; import java.io.FileOutputStream; import java.io.IOException; @@ -187,4 +188,12 @@ class Utils { } return result.toString(); } + + static Map toHeadersMap(List headers) { + Map map = new LinkedHashMap<>(); + for (HttpHeader header: headers) { + map.put(header.name.toLowerCase(), header.value); + } + return map; + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/options/ForcedColors.java b/playwright/src/main/java/com/microsoft/playwright/options/ForcedColors.java new file mode 100644 index 00000000..c78c7d35 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/options/ForcedColors.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 ForcedColors { + ACTIVE, + NONE +} \ No newline at end of file diff --git a/playwright/src/main/java/com/microsoft/playwright/options/HttpHeader.java b/playwright/src/main/java/com/microsoft/playwright/options/HttpHeader.java new file mode 100644 index 00000000..f7595336 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/options/HttpHeader.java @@ -0,0 +1,29 @@ +/* + * 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 class HttpHeader { + /** + * Name of the header. + */ + public String name; + /** + * Value of the header. + */ + public String value; + +} \ No newline at end of file diff --git a/playwright/src/main/java/com/microsoft/playwright/options/Sizes.java b/playwright/src/main/java/com/microsoft/playwright/options/Sizes.java new file mode 100644 index 00000000..11f3d0f4 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/options/Sizes.java @@ -0,0 +1,37 @@ +/* + * 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 class Sizes { + /** + * Size of the request body (POST data payload) in bytes. Set to 0 if there was no body. + */ + public int requestBodySize; + /** + * Total number of bytes from the start of the HTTP request message until (and including) the double CRLF before the body. + */ + public int requestHeadersSize; + /** + * Size of the received response body (encoded) in bytes. + */ + public int responseBodySize; + /** + * Total number of bytes from the start of the HTTP response message until (and including) the double CRLF before the body. + */ + public int responseHeadersSize; + +} \ No newline at end of file diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java index 7fc84eb3..e214f3ab 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java @@ -121,4 +121,17 @@ public class TestBrowserContextRoute extends TestBase { assertEquals("context", response.text()); context.close(); } + + @Test + void shouldSupportTheTimesParameterWithRouteMatching() { + int[] intercepted = {0}; + context.route("**/empty.html", route -> { + ++intercepted[0]; + route.resume(); + }, new BrowserContext.RouteOptions().setTimes(2)); + page.navigate(server.EMPTY_PAGE); + page.navigate(server.EMPTY_PAGE); + page.navigate(server.EMPTY_PAGE); + assertEquals(2, intercepted[0]); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java index 2e61fe1b..b73fb201 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleMisc.java @@ -73,4 +73,14 @@ public class TestElementHandleMisc extends TestBase { button.focus(); assertEquals(true, button.evaluate("button => document.activeElement === button")); } + + @Test + void shouldCheckTheBoxUsingSetChecked() { + page.setContent(""); + ElementHandle input = page.querySelector("input"); + input.setChecked(true); + assertEquals(true, page.evaluate("checkbox.checked")); + input.setChecked(false); + assertEquals(false, page.evaluate("checkbox.checked")); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestHar.java b/playwright/src/test/java/com/microsoft/playwright/TestHar.java index d88bed2a..f1484aa3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestHar.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestHar.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import static com.microsoft.playwright.Utils.getOS; import static com.microsoft.playwright.options.LoadState.DOMCONTENTLOADED; @@ -103,7 +104,7 @@ public class TestHar extends TestBase { assertEquals(1, log.getAsJsonArray("pages").size()); JsonObject pageEntry = log.getAsJsonArray("pages").get(0).getAsJsonObject(); - assertEquals("page_0", pageEntry.get("id").getAsString()); + assertNotNull(pageEntry.get("id").getAsString()); assertEquals("Hello", pageEntry.get("title").getAsString()); // expect(new Date(pageEntry.startedDateTime).valueOf()).toBeGreaterThan(Date.now() - 3600 * 1000); assertTrue(pageEntry.getAsJsonObject("pageTimings").get("onContentLoad").getAsDouble() > 0); @@ -129,7 +130,7 @@ public class TestHar extends TestBase { } assertEquals(1, log.getAsJsonArray("pages").size()); JsonObject pageEntry = log.getAsJsonArray("pages").get(0).getAsJsonObject(); - assertEquals("page_0", pageEntry.get("id").getAsString()); + assertNotNull(pageEntry.get("id").getAsString()); assertEquals("Hello", pageEntry.get("title").getAsString()); } @@ -139,7 +140,8 @@ public class TestHar extends TestBase { JsonObject log = pageWithHar.log(); assertEquals(1, log.getAsJsonArray("entries").size()); JsonObject entry = log.getAsJsonArray("entries").get(0).getAsJsonObject(); - assertEquals("page_0", entry.get("pageref").getAsString()); + String id = log.getAsJsonArray("pages").get(0).getAsJsonObject().get("id").getAsString(); + assertEquals(id, entry.get("pageref").getAsString()); assertEquals(server.EMPTY_PAGE, entry.getAsJsonObject("request").get("url").getAsString()); assertEquals("GET", entry.getAsJsonObject("request").get("method").getAsString()); assertEquals("HTTP/1.1", entry.getAsJsonObject("request").get("httpVersion").getAsString()); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java new file mode 100644 index 00000000..567b63da --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestLocatorMisc extends TestBase{ + @Test + void shouldCheckTheBoxUsingSetChecked() { + page.setContent(""); + Locator input = page.locator("input"); + input.setChecked(true); + assertEquals(true, page.evaluate("checkbox.checked")); + input.setChecked(false); + assertEquals(false, page.evaluate("checkbox.checked")); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestNetworkRequest.java b/playwright/src/test/java/com/microsoft/playwright/TestNetworkRequest.java index 0b7555df..bfccbec0 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestNetworkRequest.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestNetworkRequest.java @@ -106,8 +106,12 @@ public class TestNetworkRequest extends TestBase { return (isWebKit() && getOS() == Utils.OS.WINDOWS) || isChromium(); } + static boolean isWebKitWindows() { + return isWebKit() && getOS() == Utils.OS.WINDOWS; + } + @Test - @DisabledIf(value="isWebKitWindowsOrChromium", disabledReason="Flaky, see https://github.com/microsoft/playwright/issues/6690") + @DisabledIf(value="isWebKitWindows", disabledReason="Flaky, see https://github.com/microsoft/playwright/issues/6690") void shouldGetTheSameHeadersAsTheServer() throws ExecutionException, InterruptedException { Future serverRequest = server.futureRequest("/empty.html"); server.setRoute("/empty.html", exchange -> { @@ -119,7 +123,7 @@ public class TestNetworkRequest extends TestBase { Response response = page.navigate(server.PREFIX + "/empty.html"); Map expectedHeaders = serverRequest.get().headers.entrySet().stream().collect( Collectors.toMap(e -> e.getKey().toLowerCase(), e -> e.getValue().get(0))); - assertEquals(expectedHeaders, response.request().headers()); + assertEquals(expectedHeaders, response.request().allHeaders()); } @Test @@ -143,7 +147,7 @@ public class TestNetworkRequest extends TestBase { }); Map expectedHeaders = serverRequest.get().headers.entrySet().stream().collect( Collectors.toMap(e -> e.getKey().toLowerCase(), e -> e.getValue().get(0))); - assertEquals(expectedHeaders, response.request().headers()); + assertEquals(expectedHeaders, response.request().allHeaders()); } @Test diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java index 6d7befbc..884740d8 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java @@ -39,7 +39,7 @@ public class TestPageBasic extends TestBase { newPage.evaluate("() => new Promise(r => {})"); fail("evaluate should throw"); } catch (PlaywrightException e) { - assertTrue(e.getMessage().contains("Protocol error")); + assertTrue(e.getMessage().contains("Target closed"), e.getMessage()); } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageCheck.java b/playwright/src/test/java/com/microsoft/playwright/TestPageCheck.java new file mode 100644 index 00000000..d7ad2f78 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageCheck.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import com.microsoft.playwright.options.BoundingBox; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestPageCheck extends TestBase { + @Test + void shouldCheckTheLabelWithPosition() { + page.setContent("\n" + + " "); + BoundingBox box = page.querySelector("text=Click me").boundingBox(); + page.check("text=Click me", new Page.CheckOptions().setPosition(box.width - 10, 2)); + assertEquals(true, page.evalOnSelector("input", "input => input.checked")); + } + + @Test + void trialRunShouldNotCheck() { + page.setContent(""); + page.check("input", new Page.CheckOptions().setTrial(true)); + assertEquals(false, page.evaluate("() => window['checkbox'].checked")); + } + + @Test + void trialRunShouldNotUncheck() { + page.setContent(""); + page.uncheck("input", new Page.UncheckOptions().setTrial(true)); + assertEquals(true, page.evaluate("() => window['checkbox'].checked")); + } + + @Test + void shouldCheckTheBoxUsingSetChecked() { + page.setContent(""); + page.setChecked("input", true); + assertEquals(true, page.evaluate("() => window['checkbox'].checked")); + page.setChecked("input", false); + assertEquals(false, page.evaluate("() => window['checkbox'].checked")); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java index bd85278a..e1a3ce61 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java @@ -16,7 +16,10 @@ package com.microsoft.playwright; +import com.microsoft.playwright.options.ForcedColors; +import com.microsoft.playwright.options.ReducedMotion; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import java.util.function.Supplier; @@ -25,6 +28,7 @@ import static com.microsoft.playwright.options.ColorScheme.LIGHT; import static com.microsoft.playwright.options.Media.PRINT; import static com.microsoft.playwright.Utils.attachFrame; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TestPageEmulateMedia extends TestBase { @Test @@ -141,4 +145,30 @@ public class TestPageEmulateMedia extends TestBase { page.emulateMedia(new Page.EmulateMediaOptions().setColorScheme(LIGHT)); assertEquals("rgb(255, 255, 255)", backgroundColor.get()); } + + @Test + void shouldEmulateReducedMotion() { + assertEquals(true, page.evaluate("() => matchMedia('(prefers-reduced-motion: no-preference)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setReducedMotion(ReducedMotion.REDUCE)); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-reduced-motion: reduce)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-reduced-motion: no-preference)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setReducedMotion(ReducedMotion.NO_PREFERENCE)); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-reduced-motion: reduce)').matches")); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-reduced-motion: no-preference)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setReducedMotion(null)); + } + + @Test + @DisabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="https://bugs.webkit.org/show_bug.cgi?id=225281") + void shouldEmulateForcedColors() { + assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: none)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setForcedColors(ForcedColors.NONE)); + assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: none)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(forced-colors: active)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setForcedColors(ForcedColors.ACTIVE)); + assertEquals(false, page.evaluate("() => matchMedia('(forced-colors: none)').matches")); + assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: active)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setForcedColors(null)); + assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: none)').matches")); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java new file mode 100644 index 00000000..91a74757 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkRequest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import com.google.gson.Gson; +import com.microsoft.playwright.options.HttpHeader; +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestPageNetworkRequest extends TestBase { + @Test + void shouldReportRawHeaders() throws InterruptedException { + List serverHeaders = new ArrayList<>(); + Semaphore responseWritten = new Semaphore(0); + server.setRoute("/headers", exchange -> { + for (Map.Entry> entry : exchange.getRequestHeaders().entrySet()) { + for (String value : entry.getValue()) { + HttpHeader header = new HttpHeader(); + header.name = entry.getKey(); + header.value = value; + serverHeaders.add(header); + } + } + exchange.sendResponseHeaders(200, 0); + exchange.getResponseBody().close(); + responseWritten.release(); + }); + page.navigate(server.EMPTY_PAGE); + Request request = page.waitForRequest("**/*", () -> { + page.evaluate("() => fetch('/headers', {\n" + + " headers: [\n" + + " ['header-a', 'value-a'],\n" + + " ['header-b', 'value-b'],\n" + + " ['header-a', 'value-a-1'],\n" + + " ['header-a', 'value-a-2'],\n" + + " ]\n" + + " })"); + }); + + responseWritten.acquire(); + List expectedHeaders = serverHeaders; + if (isWebKit() && isWindows) { + expectedHeaders = expectedHeaders.stream() + .filter(h -> !"accept-encoding".equals(h.name.toLowerCase()) && !"accept-language".equals(h.name.toLowerCase())) + .collect(Collectors.toList()); + } + + List headers = request.headersArray(); + // Java HTTP server normalizes header names, work around that: + expectedHeaders = expectedHeaders.stream().map(h -> { + h.name = h.name.toLowerCase(); + return h; + }).collect(Collectors.toList()); + headers = headers.stream().map(h -> { + h.name = h.name.toLowerCase(); + return h; + }).collect(Collectors.toList()); + Comparator comparator = Comparator.comparing(h -> h.name); + expectedHeaders.sort(comparator); + headers.sort(comparator); + assertEquals(new Gson().toJsonTree(expectedHeaders), new Gson().toJsonTree(headers)); + } + + @Test + void shouldReportAllCookiesInOneHeader() { + page.navigate(server.EMPTY_PAGE); + page.evaluate("() => {\n" + + " document.cookie = 'myCookie=myValue';\n" + + " document.cookie = 'myOtherCookie=myOtherValue';\n" + + " }"); + Response response = page.navigate(server.EMPTY_PAGE); + String cookie = response.request().allHeaders().get("cookie"); + assertEquals("myCookie=myValue; myOtherCookie=myOtherValue", cookie); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkSizes.java b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkSizes.java new file mode 100644 index 00000000..0aa12458 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageNetworkSizes.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import com.microsoft.playwright.options.Sizes; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestPageNetworkSizes extends TestBase { + @Test + void shouldSetBodySizeAndHeadersSize() { + page.navigate(server.EMPTY_PAGE); + Request request = page.waitForRequest("**/*", () -> { + page.evaluate("() => fetch('./get', { method: 'POST', body: '12345' }).then(r => r.text())"); + }); + Sizes sizes = request.sizes(); + assertEquals(5, sizes.requestBodySize); + assertTrue(sizes.requestHeadersSize >= 250); + } + + @Test + void shouldSetBodySizeTo0IfThereWasNoBody() { + page.navigate(server.EMPTY_PAGE); + Request request = page.waitForRequest("**/*", + () -> page.evaluate("() => fetch('./get').then(r => r.text())")); + Sizes sizes = request.sizes(); + assertEquals(0, sizes.requestBodySize); + assertTrue(sizes.requestHeadersSize >= 200); + } + + @Test + @Disabled("responseBodySize == 16") + void shouldSetBodySizeHeadersSizeAndTransferSize() throws ExecutionException, InterruptedException { + server.setRoute("/get", exchange -> { + // In Firefox, |fetch| will be hanging until it receives |Content-Type| header + // from server. + exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8"); + exchange.sendResponseHeaders(200, 0); + try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("abc134"); + } + }); + Future request = server.futureRequest("/get"); + page.navigate(server.EMPTY_PAGE); + Response response = page.waitForResponse("**/*", + () -> page.evaluate("async () => fetch('./get').then(r => r.text())")); + request.get(); + Sizes sizes = response.request().sizes(); + assertEquals(6, sizes.responseBodySize); + assertTrue(sizes.responseHeadersSize >= 100); + } + + @Test + void shouldSetBodySizeTo0WhenThereWasNoResponseBody() { + Response response = page.navigate(server.EMPTY_PAGE); + Sizes sizes = response.request().sizes(); + assertEquals(0, sizes.responseBodySize); + assertTrue(sizes.responseHeadersSize >= 100, "" + sizes.responseHeadersSize); + } + + @Test + @Disabled("responseBodySize == 0") + void shouldHaveTheCorrectResponseBodySize() throws IOException { + Response response = page.navigate(server.PREFIX + "/simplezip.json"); + Sizes sizes = response.request().sizes(); + assertEquals(Files.size(Paths.get("src/test/resources/simplezip.json")), sizes.responseBodySize); + }} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java index c9814fd7..5a0dfda3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageRoute.java @@ -517,7 +517,13 @@ public class TestPageRoute extends TestBase { "}"); fail("did not throw"); } catch (PlaywrightException e) { - assertTrue(e.getMessage().contains("failed")); + if (isChromium()) { + assertTrue(e.getMessage().contains("Failed"), e.getMessage()); + } else if (isWebKit()) { + assertTrue(e.getMessage().contains("TypeError"), e.getMessage()); + } else if (isFirefox()) { + assertTrue(e.getMessage().contains("NetworkError"), e.getMessage()); + } } } } @@ -637,4 +643,16 @@ public class TestPageRoute extends TestBase { } } + @Test + void shouldSupportTheTimesParameterWithRouteMatching() { + int[] intercepted = {0}; + page.route("**/empty.html", route -> { + ++intercepted[0]; + route.resume(); + }, new Page.RouteOptions().setTimes(1)); + page.navigate(server.EMPTY_PAGE); + page.navigate(server.EMPTY_PAGE); + page.navigate(server.EMPTY_PAGE); + assertEquals(1, intercepted[0]); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java index cba5101d..c6f4e6a9 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java @@ -77,4 +77,23 @@ public class TestTracing extends TestBase { assertTrue(Files.exists(traceFile2)); } + @Test + void shouldWorkWithMultipleChunks(@TempDir Path tempDir) { + context.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true)); + page.navigate(server.PREFIX + "/frames/frame.html"); + + context.tracing().startChunk(); + page.setContent(""); + page.click("'Click'"); + Path traceFile1 = tempDir.resolve("trace1.zip"); + context.tracing().stopChunk(new Tracing.StopChunkOptions().setPath(traceFile1)); + + context.tracing().startChunk(); + page.hover("'Click'"); + Path traceFile2 = tempDir.resolve("trace2.zip"); + context.tracing().stopChunk(new Tracing.StopChunkOptions().setPath(traceFile2)); + + assertTrue(Files.exists(traceFile1)); + assertTrue(Files.exists(traceFile2)); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java b/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java index ee6ad25f..603f8620 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java @@ -51,7 +51,7 @@ public class TestWorkers extends TestBase { try { workerThisObj.getProperty("self"); } catch (PlaywrightException e) { - assertTrue(e.getMessage().contains("Most likely the worker has been closed.")); + assertTrue(e.getMessage().contains("Target closed"), e.getMessage()); } } diff --git a/playwright/src/test/resources/simplezip.json b/playwright/src/test/resources/simplezip.json new file mode 100644 index 00000000..5efbbae5 --- /dev/null +++ b/playwright/src/test/resources/simplezip.json @@ -0,0 +1,340 @@ +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} +{"foo": "bar"} diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index d35b6d2d..309135a9 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.15.0-next-1629487941000 +1.15.0-next-1631203211000 diff --git a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index 1fde1b0e..2c702184 100644 --- a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -252,6 +252,9 @@ class TypeRef extends Element { customTypeNames.put("BrowserContext.addCookies.cookies", "Cookie"); customTypeNames.put("BrowserContext.cookies", "Cookie"); + customTypeNames.put("Request.headersArray", "HttpHeader"); + customTypeNames.put("Response.headersArray", "HttpHeader"); + customTypeNames.put("Locator.selectOption.values", "SelectOption"); customTypeNames.put("ElementHandle.selectOption.values", "SelectOption"); customTypeNames.put("Frame.selectOption.values", "SelectOption");