diff --git a/README.md b/README.md index 8ff1b91a..6413d8b0 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 106.0.5249.30 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 107.0.5304.18 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 16.0 | ✅ | ✅ | ✅ | -| Firefox 104.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 105.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details. diff --git a/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java b/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java index 6570d532..c4810ab5 100644 --- a/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java @@ -86,6 +86,31 @@ public interface APIRequestContext { * Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update * context cookies from the response. The method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param urlOrRequest Target URL or Request to get all parameters from. */ default APIResponse fetch(String urlOrRequest) { @@ -95,6 +120,31 @@ public interface APIRequestContext { * Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update * context cookies from the response. The method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param urlOrRequest Target URL or Request to get all parameters from. * @param params Optional request parameters. */ @@ -103,6 +153,31 @@ public interface APIRequestContext { * Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update * context cookies from the response. The method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param urlOrRequest Target URL or Request to get all parameters from. */ default APIResponse fetch(Request urlOrRequest) { @@ -112,6 +187,31 @@ public interface APIRequestContext { * Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update * context cookies from the response. The method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMethod("post").setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param urlOrRequest Target URL or Request to get all parameters from. * @param params Optional request parameters. */ @@ -121,6 +221,13 @@ public interface APIRequestContext { * response. The method will populate request cookies from the context and update context cookies from the response. The * method will automatically follow redirects. * + *

Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters: + *

{@code
+   * request.get("https://example.com/api/getText", RequestOptions.create()
+   *   .setQueryParam("isbn", "1234")
+   *   .setQueryParam("page", 23));
+   * }
+ * * @param url Target URL. */ default APIResponse get(String url) { @@ -131,6 +238,13 @@ public interface APIRequestContext { * response. The method will populate request cookies from the context and update context cookies from the response. The * method will automatically follow redirects. * + *

Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters: + *

{@code
+   * request.get("https://example.com/api/getText", RequestOptions.create()
+   *   .setQueryParam("isbn", "1234")
+   *   .setQueryParam("page", 23));
+   * }
+ * * @param url Target URL. * @param params Optional request parameters. */ @@ -178,6 +292,39 @@ public interface APIRequestContext { * response. The method will populate request cookies from the context and update context cookies from the response. The * method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
+   * }
+ * + *

To send form data to the server use {@code form} option. Its value will be encoded into the request body with + * {@code application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send files): + *

{@code
+   * request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
+   *     FormData.create().set("title", "Book Title").set("body", "John Doe")
+   * ));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to upload them as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.post("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.post("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param url Target URL. */ default APIResponse post(String url) { @@ -188,6 +335,39 @@ public interface APIRequestContext { * response. The method will populate request cookies from the context and update context cookies from the response. The * method will automatically follow redirects. * + *

JSON objects can be passed directly to the request: + *

{@code
+   * Map data = new HashMap();
+   * data.put("title", "Book Title");
+   * data.put("body", "John Doe");
+   * request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
+   * }
+ * + *

To send form data to the server use {@code form} option. Its value will be encoded into the request body with + * {@code application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send files): + *

{@code
+   * request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
+   *     FormData.create().set("title", "Book Title").set("body", "John Doe")
+   * ));
+   * }
+ * + *

The common way to send file(s) in the body of a request is to upload them as form fields with {@code multipart/form-data} + * encoding. You can achieve that with Playwright API like this: + *

{@code
+   * // Pass file path to the form data constructor:
+   * Path file = Paths.get("team.csv");
+   * APIResponse response = request.post("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMultipart(
+   *     FormData.create().set("fileField", file)));
+   *
+   * // Or you can pass the file content directly as FilePayload object:
+   * FilePayload filePayload = new FilePayload("f.js", "text/javascript",
+   *       "console.log(2022);".getBytes(StandardCharsets.UTF_8));
+   * APIResponse response = request.post("https://example.com/api/uploadTeamList",
+   *   RequestOptions.create().setMultipart(
+   *     FormData.create().set("fileField", filePayload)));
+   * }
+ * * @param url Target URL. * @param params Optional request parameters. */ diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index acb80d75..4a38dbda 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -143,7 +143,7 @@ public interface Browser extends AutoCloseable { public Proxy proxy; /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public HarContentPolicy recordHarContent; @@ -381,7 +381,7 @@ public interface Browser extends AutoCloseable { } /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public NewContextOptions setRecordHarContent(HarContentPolicy recordHarContent) { @@ -623,7 +623,7 @@ public interface Browser extends AutoCloseable { public Proxy proxy; /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public HarContentPolicy recordHarContent; @@ -861,7 +861,7 @@ public interface Browser extends AutoCloseable { } /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public NewPageOptions setRecordHarContent(HarContentPolicy recordHarContent) { diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index b1ec9612..8f4f54b8 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -404,7 +404,7 @@ public interface BrowserContext extends AutoCloseable { * "\n" + * "\n" + * "
"); - * page.locator("button").click(); + * page.getByRole("button").click(); * } * } * } @@ -463,7 +463,7 @@ public interface BrowserContext extends AutoCloseable { * "\n" + * "\n" + * "
"); - * page.locator("button").click(); + * page.getByRole("button").click(); * } * } * } @@ -533,7 +533,7 @@ public interface BrowserContext extends AutoCloseable { * "\n" + * "\n" + * "
\n"); - * page.locator("button").click(); + * page.getByRole("button").click(); * } * } * } diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index 885b98ce..78606ae1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -515,7 +515,7 @@ public interface BrowserType { public Proxy proxy; /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public HarContentPolicy recordHarContent; @@ -861,7 +861,7 @@ public interface BrowserType { } /** * Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach} - * is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + * is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. * Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification. */ public LaunchPersistentContextOptions setRecordHarContent(HarContentPolicy recordHarContent) { diff --git a/playwright/src/main/java/com/microsoft/playwright/Download.java b/playwright/src/main/java/com/microsoft/playwright/Download.java index 220f36be..3a74d7eb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Download.java +++ b/playwright/src/main/java/com/microsoft/playwright/Download.java @@ -27,14 +27,8 @@ import java.nio.file.Path; *

Download event is emitted once the download starts. Download path becomes available once download completes: *

{@code
  * // wait for download to start
- * Download download  = page.waitForDownload(() -> page.locator("a").click());
- * // wait for download to complete
- * Path path = download.path();
- * }
- *
{@code
- * // wait for download to start
  * Download download = page.waitForDownload(() -> {
- *   page.locator("a").click();
+ *   page.getByText("Download file").click();
  * });
  * // wait for download to complete
  * Path path = download.path();
diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
index 0e3081e7..2e6ed98b 100644
--- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
+++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
@@ -51,7 +51,7 @@ import java.util.*;
  * 

With the locator, every time the {@code element} is used, up-to-date DOM element is located in the page using the selector. So * in the snippet below, underlying DOM element is going to be located twice. *

{@code
- * Locator locator = page.locator("text=Submit");
+ * Locator locator = page.getByText("Submit");
  * locator.hover();
  * locator.click();
  * }
@@ -1438,8 +1438,8 @@ public interface ElementHandle extends JSHandle { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default Object evalOnSelector(String selector, String expression) { return evalOnSelector(selector, expression, null); @@ -1464,8 +1464,8 @@ public interface ElementHandle extends JSHandle { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ Object evalOnSelector(String selector, String expression, Object arg); @@ -1489,8 +1489,8 @@ public interface ElementHandle extends JSHandle { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default Object evalOnSelectorAll(String selector, String expression) { return evalOnSelectorAll(selector, expression, null); @@ -1515,8 +1515,8 @@ public interface ElementHandle extends JSHandle { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ Object evalOnSelectorAll(String selector, String expression, Object arg); diff --git a/playwright/src/main/java/com/microsoft/playwright/FileChooser.java b/playwright/src/main/java/com/microsoft/playwright/FileChooser.java index 2447b7aa..a85a4f6e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/FileChooser.java +++ b/playwright/src/main/java/com/microsoft/playwright/FileChooser.java @@ -22,7 +22,7 @@ import java.nio.file.Path; /** * {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event. *
{@code
- * FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
+ * FileChooser fileChooser = page.waitForFileChooser(() -> page.getByText("Upload").click());
  * fileChooser.setFiles(Paths.get("myfile.pdf"));
  * }
*/ diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index 2336eea6..8ef8be14 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -794,6 +794,216 @@ public interface Frame { return this; } } + class GetByAltTextOptions { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public Boolean exact; + + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public GetByAltTextOptions setExact(boolean exact) { + this.exact = exact; + return this; + } + } + class GetByLabelOptions { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public Boolean exact; + + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public GetByLabelOptions setExact(boolean exact) { + this.exact = exact; + return this; + } + } + class GetByPlaceholderOptions { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public Boolean exact; + + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public GetByPlaceholderOptions setExact(boolean exact) { + this.exact = exact; + return this; + } + } + class GetByRoleOptions { + /** + * An attribute that is usually set by {@code aria-checked} or native {@code } controls. Available values for + * checked are {@code true}, {@code false} and {@code "mixed"}. + * + *

Learn more about {@code aria-checked}. + */ + public Boolean checked; + /** + * A boolean attribute that is usually set by {@code aria-disabled} or {@code disabled}. + * + *

NOTE: Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about {@code aria-disabled}. + */ + public Boolean disabled; + /** + * A boolean attribute that is usually set by {@code aria-expanded}. + * + *

Learn more about {@code aria-expanded}. + */ + public Boolean expanded; + /** + * A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as defined by ARIA, are matched by role selector. + * + *

Learn more about {@code aria-hidden}. + */ + public Boolean includeHidden; + /** + * A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem}, with default values for + * {@code

-

} elements. + * + *

Learn more about {@code aria-level}. + */ + public Integer level; + /** + * A string attribute that matches accessible name. + * + *

Learn more about accessible name. + */ + public Object name; + /** + * An attribute that is usually set by {@code aria-pressed}. Available values for pressed are {@code true}, {@code false} and {@code "mixed"}. + * + *

Learn more about {@code aria-pressed}. + */ + public Boolean pressed; + /** + * A boolean attribute that is usually set by {@code aria-selected}. + * + *

Learn more about {@code aria-selected}. + */ + public Boolean selected; + + /** + * An attribute that is usually set by {@code aria-checked} or native {@code } controls. Available values for + * checked are {@code true}, {@code false} and {@code "mixed"}. + * + *

Learn more about {@code aria-checked}. + */ + public GetByRoleOptions setChecked(boolean checked) { + this.checked = checked; + return this; + } + /** + * A boolean attribute that is usually set by {@code aria-disabled} or {@code disabled}. + * + *

NOTE: Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about {@code aria-disabled}. + */ + public GetByRoleOptions setDisabled(boolean disabled) { + this.disabled = disabled; + return this; + } + /** + * A boolean attribute that is usually set by {@code aria-expanded}. + * + *

Learn more about {@code aria-expanded}. + */ + public GetByRoleOptions setExpanded(boolean expanded) { + this.expanded = expanded; + return this; + } + /** + * A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as defined by ARIA, are matched by role selector. + * + *

Learn more about {@code aria-hidden}. + */ + public GetByRoleOptions setIncludeHidden(boolean includeHidden) { + this.includeHidden = includeHidden; + return this; + } + /** + * A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem}, with default values for + * {@code

-

} elements. + * + *

Learn more about {@code aria-level}. + */ + public GetByRoleOptions setLevel(int level) { + this.level = level; + return this; + } + /** + * A string attribute that matches accessible name. + * + *

Learn more about accessible name. + */ + public GetByRoleOptions setName(String name) { + this.name = name; + return this; + } + /** + * A string attribute that matches accessible name. + * + *

Learn more about accessible name. + */ + public GetByRoleOptions setName(Pattern name) { + this.name = name; + return this; + } + /** + * An attribute that is usually set by {@code aria-pressed}. Available values for pressed are {@code true}, {@code false} and {@code "mixed"}. + * + *

Learn more about {@code aria-pressed}. + */ + public GetByRoleOptions setPressed(boolean pressed) { + this.pressed = pressed; + return this; + } + /** + * A boolean attribute that is usually set by {@code aria-selected}. + * + *

Learn more about {@code aria-selected}. + */ + public GetByRoleOptions setSelected(boolean selected) { + this.selected = selected; + return this; + } + } + class GetByTextOptions { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public Boolean exact; + + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public GetByTextOptions setExact(boolean exact) { + this.exact = exact; + return this; + } + } + class GetByTitleOptions { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public Boolean exact; + + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + public GetByTitleOptions setExact(boolean exact) { + this.exact = exact; + return this; + } + } class NavigateOptions { /** * Referer header value. If provided it will take preference over the referer header value set by {@link @@ -2409,8 +2619,8 @@ public interface Frame { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ default Object evalOnSelector(String selector, String expression, Object arg) { @@ -2439,8 +2649,8 @@ public interface Frame { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default Object evalOnSelector(String selector, String expression) { return evalOnSelector(selector, expression, null); @@ -2468,8 +2678,8 @@ public interface Frame { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ Object evalOnSelector(String selector, String expression, Object arg, EvalOnSelectorOptions options); @@ -2494,8 +2704,8 @@ public interface Frame { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default Object evalOnSelectorAll(String selector, String expression) { return evalOnSelectorAll(selector, expression, null); @@ -2521,8 +2731,8 @@ public interface Frame { * * @param selector A selector to query for. See working with selectors for more * details. - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ Object evalOnSelectorAll(String selector, String expression, Object arg); @@ -2555,8 +2765,8 @@ public interface Frame { * bodyHandle.dispose(); * }

* - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default Object evaluate(String expression) { return evaluate(expression, null); @@ -2590,8 +2800,8 @@ public interface Frame { * bodyHandle.dispose(); * } * - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ Object evaluate(String expression, Object arg); @@ -2622,8 +2832,8 @@ public interface Frame { * resultHandle.dispose(); * } * - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. */ default JSHandle evaluateHandle(String expression) { return evaluateHandle(expression, null); @@ -2655,8 +2865,8 @@ public interface Frame { * resultHandle.dispose(); * } * - * @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - * as a function. Otherwise, evaluated as an expression. + * @param expression JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + * automatically invoked. * @param arg Optional argument to pass to {@code expression}. */ JSHandle evaluateHandle(String expression, Object arg); @@ -2733,7 +2943,7 @@ public interface Frame { * that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code ").setContentType("text/html"))); page.route("**/iframe.html", route -> { route.fulfill(new Route.FulfillOptions().setBody("\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - " 1\n" + - " 2\n" + - " ").setContentType("text/html")); + "
\n" + + " \n" + + " \n" + + "
\n" + + " 1\n" + + " 2\n" + + " \n" + + "").setContentType("text/html")); }); page.route("**/iframe-2.html", route -> { route.fulfill(new Route.FulfillOptions().setBody("").setContentType("text/html")); @@ -241,4 +243,24 @@ public class TestLocatorFrame extends TestBase { assertThat(button3).hasText("Hello from iframe-3.html"); } + @Test + void getByCoverage() { + routeIframe(page); + page.navigate(server.EMPTY_PAGE); + Locator button1 = page.frameLocator("iframe").getByRole(AriaRole.BUTTON); + Locator button2 = page.frameLocator("iframe").getByText("Hello"); + Locator button3 = page.frameLocator("iframe").getByTestId("buttonId"); + assertThat(button1).hasText("Hello iframe"); + assertThat(button2).hasText("Hello iframe"); + assertThat(button3).hasText("Hello iframe"); + Locator input1 = page.frameLocator("iframe").getByLabel("Name"); + assertThat(input1).hasValue(""); + Locator input2 = page.frameLocator("iframe").getByPlaceholder("Placeholder"); + assertThat(input2).hasValue(""); + Locator input3 = page.frameLocator("iframe").getByAltText("Alternative"); + assertThat(input3).hasValue(""); + Locator input4 = page.frameLocator("iframe").getByTitle("Title"); + assertThat(input4).hasValue(""); + } + } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestSelectorsGetBy.java b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsGetBy.java new file mode 100644 index 00000000..87a928a8 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsGetBy.java @@ -0,0 +1,142 @@ +package com.microsoft.playwright; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestSelectorsGetBy extends TestBase { + @Test + void getByTestIdShouldWork() { + page.setContent("
Hello world
"); + assertThat(page.getByTestId("Hello")).hasText("Hello world"); + assertThat(page.mainFrame().getByTestId("Hello")).hasText("Hello world"); + assertThat(page.locator("div").getByTestId("Hello")).hasText("Hello world"); + } + + @Test + void getByTestIdShouldEscapeId() { + page.setContent("
Hello world
"); + assertThat(page.getByTestId("He\"llo")).hasText("Hello world"); + } + + @Test + void getByTextShouldWork() { + page.setContent("
yo
ya
\nye
"); + assertTrue(((String) page.getByText("ye").evaluate("e => e.outerHTML")).contains(">\nye ")); + assertTrue(((String) page.getByText(Pattern.compile("ye")).evaluate("e => e.outerHTML")).contains(">\nye ")); + assertTrue(((String) page.getByText(Pattern.compile("e")).evaluate("e => e.outerHTML")).contains(">\nye ")); + + page.setContent("
ye
ye
"); + assertTrue(((String) page.getByText("ye", new Page.GetByTextOptions().setExact(true)).first().evaluate("e => e.outerHTML")).contains("> ye ")); + + page.setContent("
Hello world
Hello
"); + assertEquals("
Hello
", page.getByText("Hello", new Page.GetByTextOptions().setExact(true)).evaluate("e => e.outerHTML")); + } + + @Test + void getByLabelShouldWork() { + page.setContent("
"); + assertEquals("LABEL", page.getByText("Name").evaluate("e => e.nodeName")); + assertEquals("INPUT", page.getByLabel("Name").evaluate("e => e.nodeName")); + assertEquals("INPUT", page.mainFrame().getByLabel("Name").evaluate("e => e.nodeName")); + assertEquals("INPUT", page.locator("div").getByLabel("Name").evaluate("e => e.nodeName")); + } + + @Test + void getByLabelShouldWorkWithNestedElements() { + page.setContent(""); + + assertThat(page.getByLabel("last name")).hasAttribute("id", "target"); + assertThat(page.getByLabel("st na")).hasAttribute("id", "target"); + assertThat(page.getByLabel("Name")).hasAttribute("id", "target"); + assertThat(page.getByLabel("Last Name", new Page.GetByLabelOptions().setExact(true))).hasAttribute("id", "target"); + assertThat(page.getByLabel(Pattern.compile("Last\\s+name", Pattern.CASE_INSENSITIVE))).hasAttribute("id", "target"); + + assertEquals(Collections.emptyList(), page.getByLabel("Last", new Page.GetByLabelOptions().setExact(true)).elementHandles()); + assertEquals(Collections.emptyList(), page.getByLabel("last name", new Page.GetByLabelOptions().setExact(true)).elementHandles()); + assertEquals(Collections.emptyList(), page.getByLabel("Name", new Page.GetByLabelOptions().setExact(true)).elementHandles()); + assertEquals(Collections.emptyList(), page.getByLabel("what?").elementHandles()); + assertEquals(Collections.emptyList(), page.getByLabel(Pattern.compile("last name")).elementHandles()); + } + + @Test + void getByPlaceholderShouldWork() { + page.setContent("
\n" + + " \n" + + " \n" + + "
"); + assertThat(page.getByPlaceholder("hello")).hasCount(2); + assertThat(page.getByPlaceholder("Hello", new Page.GetByPlaceholderOptions().setExact(true))).hasCount(1); + assertThat(page.getByPlaceholder(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))).hasCount(1); + + // Coverage + assertThat(page.mainFrame().getByPlaceholder("hello")).hasCount(2); + assertThat(page.locator("div").getByPlaceholder("hello")).hasCount(2); + } + + @Test + void getByAltTextShouldWork() { + page.setContent("
\n" + + " \n" + + " \n" + + "
"); + assertThat(page.getByAltText("hello")).hasCount(2); + assertThat(page.getByAltText("Hello", new Page.GetByAltTextOptions().setExact(true))).hasCount(1); + assertThat(page.getByAltText(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))).hasCount(1); + + // Coverage + assertThat(page.mainFrame().getByAltText("hello")).hasCount(2); + assertThat(page.locator("div").getByAltText("hello")).hasCount(2); + } + + @Test + void getByTitleShouldWork() { + page.setContent("
\n" + + " \n" + + " \n" + + "
"); + assertThat(page.getByTitle("hello")).hasCount(2); + assertThat(page.getByTitle("Hello", new Page.GetByTitleOptions().setExact(true))).hasCount(1); + assertThat(page.getByTitle(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))).hasCount(1); + + // Coverage + assertThat(page.mainFrame().getByTitle("hello")).hasCount(2); + assertThat(page.locator("div").getByTitle("hello")).hasCount(2); + } + + @Test + void getByEscaping() { + page.setContent(""); + page.evalOnSelector("input", "input => {\n" + + " input.setAttribute('placeholder', 'hello my\\nwo\"rld');\n" + + " input.setAttribute('title', 'hello my\\nwo\"rld');\n" + + " input.setAttribute('alt', 'hello my\\nwo\"rld');\n" + + " }"); + assertThat(page.getByText("hello my\nwo\"rld")).hasAttribute("id", "label"); + assertThat(page.getByText("hello my wo\"rld")).hasAttribute("id", "label"); + assertThat(page.getByLabel("hello my\nwo\"rld")).hasAttribute("id", "control"); + assertThat(page.getByPlaceholder("hello my\nwo\"rld")).hasAttribute("id", "control"); + assertThat(page.getByAltText("hello my\nwo\"rld")).hasAttribute("id", "control"); + assertThat(page.getByTitle("hello my\nwo\"rld")).hasAttribute("id", "control"); + + page.setContent(""); + page.evalOnSelector("input", "input => {\n" + + " input.setAttribute('placeholder', 'hello my\\nworld');\n" + + " input.setAttribute('title', 'hello my\\nworld');\n" + + " input.setAttribute('alt', 'hello my\\nworld');\n" + + " }"); + assertThat(page.getByText("hello my\nworld")).hasAttribute("id", "label"); + assertThat(page.getByText("hello my world")).hasAttribute("id", "label"); + assertThat(page.getByLabel("hello my\nworld")).hasAttribute("id", "control"); + assertThat(page.getByPlaceholder("hello my\nworld")).hasAttribute("id", "control"); + assertThat(page.getByAltText("hello my\nworld")).hasAttribute("id", "control"); + assertThat(page.getByTitle("hello my\nworld")).hasAttribute("id", "control"); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRole.java b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRole.java new file mode 100644 index 00000000..2e3fa404 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestSelectorsRole.java @@ -0,0 +1,385 @@ +package com.microsoft.playwright; + +import com.microsoft.playwright.options.AriaRole; +import org.junit.jupiter.api.Test; + +import java.util.regex.Pattern; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestSelectorsRole extends TestBase { + @Test + void shouldDetectRoles() { + page.setContent("\n" + + " \n" + + " \n" + + "

Heading

\n" + + "
Hello
\n" + + "
I am a dialog
"); + assertEquals(asList(""), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList(""), page.locator("role=listbox").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList(""), page.locator("role=combobox").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList("

Heading

"), page.locator("role=heading").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList("
Hello
"), page.locator("role=group").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList("
I am a dialog
"), page.locator("role=dialog").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(emptyList(), page.locator("role=menuitem").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(emptyList(), page.getByRole(AriaRole.MENUITEM).evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportSelected() { + page.setContent("\n" + + "
\n" + + "
Hi
\n" + + "
Hello
\n" + + "
"); + assertEquals(asList( + "", + "
Hi
" + ), page.locator("role=option[selected]").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "", + "
Hi
" + ), page.locator("role=option[selected=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "", + "
Hi
" + ), page.getByRole(AriaRole.OPTION, new Page.GetByRoleOptions().setSelected(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "", + "
Hello
" + ), page.locator("role=option[selected=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "
Hello
" + ), page.getByRole(AriaRole.OPTION, new Page.GetByRoleOptions().setSelected(false)).evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportChecked() { + page.setContent("\n" + + " \n" + + " \n" + + "
Hi
\n" + + "
Hello
\n" + + "
Unknown
"); + page.evalOnSelector("[indeterminate]", "input => input.indeterminate = true"); + + assertEquals(asList( + "", + "
Hi
" + ), page.locator("role=checkbox[checked]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "
Hi
" + ), page.locator("role=checkbox[checked=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "
Hi
" + ), page.getByRole(AriaRole.CHECKBOX, new Page.GetByRoleOptions().setChecked(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "", + "
Hello
", + "
Unknown
" + ), page.locator("role=checkbox[checked=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "
Hello
", + "
Unknown
" + ), page.getByRole(AriaRole.CHECKBOX, new Page.GetByRoleOptions().setChecked(false)).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "" + ), page.locator("role=checkbox[checked='mixed']").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "", + "
Hi
", + "
Hello
", + "
Unknown
" + ), page.locator("role=checkbox").evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportPressed() { + page.setContent("\n" + + " \n" + + " \n" + + " "); + assertEquals(asList( + "" + ), page.locator("role=button[pressed]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "" + ), page.locator("role=button[pressed=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setPressed(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.locator("role=button[pressed=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setPressed(false)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "" + ), page.locator("role=button[pressed='mixed']").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "", + "" + ), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportExpanded() { + page.setContent("\n" + + " \n" + + " "); + assertEquals(asList( + "" + ), page.locator("role=button[expanded]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "" + ), page.locator("role=button[expanded=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setExpanded(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.locator("role=button[expanded=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setExpanded(false)).evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportDisabled() { + page.setContent("\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
"); + assertEquals(asList( + "", + "", + "" + ), page.locator("role=button[disabled]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "" + ), page.locator("role=button[disabled=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setDisabled(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.locator("role=button[disabled=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setDisabled(false)).evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportLevel() { + page.setContent("

Hello

\n" + + "

Hi

\n" + + "
Bye
"); + assertEquals(asList( + "

Hello

" + ), page.locator("role=heading[level=1]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "

Hello

" + ), page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setLevel(1)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "

Hi

" + ), page.locator("role=heading[level=3]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "

Hi

" + ), page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setLevel(3)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
Bye
" + ), page.locator("role=heading[level=5]").evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldFilterHiddenUnlessExplicitlyAskedFor() { + page.setContent("\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " "); + assertEquals(asList( + "", + "", + "", + "" + ), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ), page.locator("role=button[include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ), page.locator("role=button[include-hidden=true]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "", + "", + "", + "" + ), page.locator("role=button[include-hidden=false]").evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void shouldSupportName() { + page.setContent("
\n" + + "
\n" + + "
\n" + + "
\n" + + "
"); + assertEquals(asList( + "
" + ), page.locator("role=button[name='Hello']").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hello")).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "
" + ), page.locator("role=button[name*='all']").evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "
", + "
" + ), page.locator("role=button[name=/^H[ae]llo$/]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
", + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("^H[ae]llo$"))).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "
", + "
" + ), page.locator("role=button[name=/h.*o/i]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
", + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("h.*o", Pattern.CASE_INSENSITIVE))).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "
", + "
" + ), page.locator("role=button[name='Hello'][include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
", + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hello").setIncludeHidden(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
", + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("hello").setIncludeHidden(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + + assertEquals(asList( + "
" + ), page.locator("role=button[name=Hello]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
" + ), page.locator("role=button[name=123][include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)")); + assertEquals(asList( + "
" + ), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("123").setIncludeHidden(true)).evaluateAll("els => els.map(e => e.outerHTML)")); + } + + @Test + void errors() { + PlaywrightException e0 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=[bar]")); + assertTrue(e0.getMessage().contains("Role must not be empty"), e0.getMessage()); + + PlaywrightException e1 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=foo[sElected]")); + assertTrue(e1.getMessage().contains("Unknown attribute \"sElected\", must be one of \"checked\", \"disabled\", \"expanded\", \"include-hidden\", \"level\", \"name\", \"pressed\", \"selected\""), e1.getMessage()); + + PlaywrightException e2 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=foo[bar . qux=true]")); + assertTrue(e2.getMessage().contains("Unknown attribute \"bar.qux\""), e2.getMessage()); + + PlaywrightException e3 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=heading[level='bar']")); + assertTrue(e3.getMessage().contains("\"level\" attribute must be compared to a number"), e3.getMessage()); + + PlaywrightException e4 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=checkbox[checked='bar']")); + assertTrue(e4.getMessage().contains("\"checked\" must be one of true, false, \"mixed\""), e4.getMessage()); + + PlaywrightException e5 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=checkbox[checked~=true]")); + assertTrue(e5.getMessage().contains("cannot use ~= in attribute with non-string matching value"), e5.getMessage()); + + PlaywrightException e6 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=button[level=3]")); + assertTrue(e6.getMessage().contains("\"level\" attribute is only supported for roles: \"heading\", \"listitem\", \"row\", \"treeitem\""), e6.getMessage()); + + PlaywrightException e7 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=button[name]")); + assertTrue(e7.getMessage().contains("\"name\" attribute must have a value"), e7.getMessage()); + } + +} diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index 5ff8c4f5..cd818323 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.26.0 +1.27.0-beta-1665092193000 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 da152604..2a874682 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 @@ -939,7 +939,7 @@ class Interface extends TypeDefinition { if (methods.stream().anyMatch(m -> "create".equals(m.jsonName))) { output.add("import com.microsoft.playwright.impl." + jsonName + "Impl;"); } - if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard").contains(jsonName)) { + if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "FrameLocator", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard").contains(jsonName)) { output.add("import com.microsoft.playwright.options.*;"); } if ("Download".equals(jsonName)) {