1
0
mirror of synced 2026-05-23 19:23:20 +00:00

Compare commits

...

23 Commits

Author SHA1 Message Date
Yury Semikhatsky 71ea72ba53 chore: bump version to 1.28.1 (#1134) 2022-11-28 16:14:43 -08:00
Yury Semikhatsky 7d69f7a087 chore: roll driver to 1.28.1 (#1133) 2022-11-28 16:01:51 -08:00
Yury Semikhatsky 04158db747 cherry-pick(#1131): fix: implement LocatorImpl.getByRole (#1132)
Fixes https://github.com/microsoft/playwright-java/issues/1130
2022-11-28 15:13:25 -08:00
Yury Semikhatsky e47d0ab16c chore: set release version to 1.28.0 (#1125) 2022-11-16 12:42:59 -08:00
Yury Semikhatsky a061ba0f30 chore: roll driver to 1.28.0 (#1124) 2022-11-16 11:47:17 -08:00
Yury Semikhatsky 5a3daf64b9 chore: roll driver to 1.28.0-beta-1668481322000 (#1123) 2022-11-14 22:08:11 -08:00
Yury Semikhatsky 170d07a005 feat: roll driver to 1.29.0-alpha-1668454236000 (#1121) 2022-11-14 14:16:32 -08:00
Yury Semikhatsky 1674f95bd1 fix: do not fail on a bad file name in stack trace (#1120) 2022-11-11 18:42:11 -08:00
Yury Semikhatsky 3cc198ea26 fix: NPE in setInputFiles (#1113) 2022-11-04 16:55:01 -07:00
Yury Semikhatsky 071ca8b90c test: cleanup user-data-dir after tests in TestDefaultBrowserContext2 (#1112) 2022-11-04 16:16:51 -07:00
Max Schmitt 805fa3a8cb devops: update repo for internal tests 2022-10-30 21:30:22 -07:00
Yury Semikhatsky 1825c13fde fix: use internal:text* selectors (#1108) 2022-10-26 11:18:13 -07:00
Yury Semikhatsky 48d9528675 chore: roll driver to 1.28.0-alpha-oct-26-2022 (#1106) 2022-10-26 10:34:05 -07:00
Yury Semikhatsky 4275ee3455 feat(docker): set JAVA_HOME to openjdk 17 (#1105) 2022-10-25 10:38:11 -07:00
Yury Semikhatsky 69f81ea8e9 docs: update version in readme 2022-10-10 12:20:58 -07:00
Yury Semikhatsky 261160e4cf chore: bump dev version to 1.28.0-SNAPSHOT (#1094) 2022-10-07 17:29:18 -07:00
Jonathan Leitschuh 9ac9347dc5 [SECURITY] Fix Zip Slip Vulnerability (#1078) 2022-10-07 17:21:37 -07:00
Yury Semikhatsky 02ac0380a8 chore: roll driver to 1.27.0 (#1092) 2022-10-07 17:21:15 -07:00
Yury Semikhatsky bb4f3297e8 feat: roll 1.27.0 alpha oct 5 2022 (#1091) 2022-10-07 16:20:04 -07:00
Yury Semikhatsky ae54a7da55 docs: update gradle snippet 2022-09-20 16:23:54 -07:00
Yury Semikhatsky c6192db180 docs: update version in README.md to 1.26.0 2022-09-20 16:21:18 -07:00
Yury Semikhatsky b5c09a3141 chore: set dev version to 1.27.0-SNAPSHOT (#1073) 2022-09-20 15:32:03 -07:00
Yury Semikhatsky 8ef960a5f7 chore: mark 1.26.0 (#1072) 2022-09-20 14:28:40 -07:00
63 changed files with 4132 additions and 386 deletions
+1 -1
View File
@@ -16,6 +16,6 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GH_TOKEN}" \
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
https://api.github.com/repos/microsoft/playwright-internal/dispatches
https://api.github.com/repos/microsoft/playwright-browsers/dispatches
env:
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
+6 -6
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->106.0.5249.30<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->104.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->108.0.5359.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->106.0<!-- GEN:stop --> | :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.
@@ -43,15 +43,15 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.17.0</version>
<version>1.27.0</version>
</dependency>
```
To run Playwright using Gradle add following dependency to your build.gradle file:
```json lines
```gradle
dependencies {
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.25.0'
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.27.0'
}
```
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -17,13 +17,11 @@
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>driver</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>playwright</artifactId>
@@ -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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> 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));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> 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));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> 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));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> 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));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @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.
*
* <p> Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters:
* <pre>{@code
* request.get("https://example.com/api/getText", RequestOptions.create()
* .setQueryParam("isbn", "1234")
* .setQueryParam("page", 23));
* }</pre>
*
* @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.
*
* <p> Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters:
* <pre>{@code
* request.get("https://example.com/api/getText", RequestOptions.create()
* .setQueryParam("isbn", "1234")
* .setQueryParam("page", 23));
* }</pre>
*
* @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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
* }</pre>
*
* <p> 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):
* <pre>{@code
* request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
* FormData.create().set("title", "Book Title").set("body", "John Doe")
* ));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @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.
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
* }</pre>
*
* <p> 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):
* <pre>{@code
* request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
* FormData.create().set("title", "Book Title").set("body", "John Doe")
* ));
* }</pre>
*
* <p> 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:
* <pre>{@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)));
* }</pre>
*
* @param url Target URL.
* @param params Optional request parameters.
*/
@@ -81,9 +81,10 @@ public interface Browser extends AutoCloseable {
public Boolean bypassCSP;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
public Optional<ColorScheme> colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
@@ -94,9 +95,9 @@ public interface Browser extends AutoCloseable {
public Map<String, String> 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public ForcedColors forcedColors;
public Optional<ForcedColors> forcedColors;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
@@ -143,7 +144,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;
@@ -176,9 +177,10 @@ public interface Browser extends AutoCloseable {
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
public Optional<ReducedMotion> reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
@@ -258,10 +260,11 @@ public interface Browser extends AutoCloseable {
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public NewContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
@@ -280,10 +283,10 @@ public interface Browser extends AutoCloseable {
}
/**
* 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public NewContextOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
this.forcedColors = Optional.ofNullable(forcedColors);
return this;
}
public NewContextOptions setGeolocation(double latitude, double longitude) {
@@ -381,7 +384,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) {
@@ -447,10 +450,11 @@ public interface Browser extends AutoCloseable {
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public NewContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
this.reducedMotion = Optional.ofNullable(reducedMotion);
return this;
}
/**
@@ -561,9 +565,10 @@ public interface Browser extends AutoCloseable {
public Boolean bypassCSP;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
public Optional<ColorScheme> colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
@@ -574,9 +579,9 @@ public interface Browser extends AutoCloseable {
public Map<String, String> 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public ForcedColors forcedColors;
public Optional<ForcedColors> forcedColors;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
@@ -623,7 +628,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;
@@ -656,9 +661,10 @@ public interface Browser extends AutoCloseable {
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
public Optional<ReducedMotion> reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
@@ -738,10 +744,11 @@ public interface Browser extends AutoCloseable {
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public NewPageOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
@@ -760,10 +767,10 @@ public interface Browser extends AutoCloseable {
}
/**
* 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public NewPageOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
this.forcedColors = Optional.ofNullable(forcedColors);
return this;
}
public NewPageOptions setGeolocation(double latitude, double longitude) {
@@ -861,7 +868,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) {
@@ -927,10 +934,11 @@ public interface Browser extends AutoCloseable {
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public NewPageOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
this.reducedMotion = Optional.ofNullable(reducedMotion);
return this;
}
/**
@@ -185,7 +185,8 @@ public interface BrowserContext extends AutoCloseable {
*/
public HarNotFound notFound;
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when {@link BrowserContext#close BrowserContext.close()} is called.
*/
public Boolean update;
/**
@@ -207,7 +208,8 @@ public interface BrowserContext extends AutoCloseable {
return this;
}
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when {@link BrowserContext#close BrowserContext.close()} is called.
*/
public RouteFromHAROptions setUpdate(boolean update) {
this.update = update;
@@ -404,7 +406,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.locator("button").click();
* page.getByRole("button").click();
* }
* }
* }
@@ -463,7 +465,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.locator("button").click();
* page.getByRole("button").click();
* }
* }
* }
@@ -533,7 +535,7 @@ public interface BrowserContext extends AutoCloseable {
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>\n");
* page.locator("button").click();
* page.getByRole("button").click();
* }
* }
* }
@@ -407,9 +407,10 @@ public interface BrowserType {
public Boolean chromiumSandbox;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
public Optional<ColorScheme> colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
@@ -441,9 +442,9 @@ public interface BrowserType {
public Map<String, String> 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public ForcedColors forcedColors;
public Optional<ForcedColors> forcedColors;
public Geolocation geolocation;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
@@ -515,7 +516,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;
@@ -548,9 +549,10 @@ public interface BrowserType {
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
public Optional<ReducedMotion> reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
@@ -666,10 +668,11 @@ public interface BrowserType {
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
*/
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
@@ -721,10 +724,10 @@ public interface BrowserType {
}
/**
* 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"}.
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code "none"}.
*/
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
this.forcedColors = Optional.ofNullable(forcedColors);
return this;
}
public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) {
@@ -861,7 +864,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) {
@@ -927,10 +930,11 @@ public interface BrowserType {
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
*/
public LaunchPersistentContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
this.reducedMotion = Optional.ofNullable(reducedMotion);
return this;
}
/**
@@ -27,14 +27,8 @@ import java.nio.file.Path;
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.locator("a").click());
* // wait for download to complete
* Path path = download.path();
* }</pre>
* <pre>{@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();
@@ -51,7 +51,7 @@ import java.util.*;
* <p> 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.
* <pre>{@code
* Locator locator = page.locator("text=Submit");
* Locator locator = page.getByText("Submit");
* locator.hover();
* locator.click();
* }</pre>
@@ -438,6 +438,12 @@ public interface ElementHandle extends JSHandle {
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* 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.
@@ -472,6 +478,15 @@ public interface ElementHandle extends JSHandle {
this.modifiers = modifiers;
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 HoverOptions 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.
@@ -607,7 +622,7 @@ public interface ElementHandle extends JSHandle {
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -680,7 +695,7 @@ public interface ElementHandle extends JSHandle {
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -1438,8 +1453,8 @@ public interface ElementHandle extends JSHandle {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelector(String selector, String expression) {
return evalOnSelector(selector, expression, null);
@@ -1464,8 +1479,8 @@ public interface ElementHandle extends JSHandle {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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 +1504,8 @@ public interface ElementHandle extends JSHandle {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelectorAll(String selector, String expression) {
return evalOnSelectorAll(selector, expression, null);
@@ -1515,8 +1530,8 @@ public interface ElementHandle extends JSHandle {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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);
@@ -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.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
* FileChooser fileChooser = page.waitForFileChooser(() -> page.getByText("Upload").click());
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
@@ -794,6 +794,240 @@ public interface Frame {
return this;
}
}
class GetByAltTextOptions {
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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 <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public Boolean expanded;
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public Integer level;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public Object name;
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public Boolean pressed;
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
public Boolean selected;
/**
* An attribute that is usually set by {@code aria-checked} or native {@code <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public GetByRoleOptions setChecked(boolean checked) {
this.checked = checked;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public GetByRoleOptions setDisabled(boolean disabled) {
this.disabled = disabled;
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public GetByRoleOptions setExpanded(boolean expanded) {
this.expanded = expanded;
return this;
}
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public GetByRoleOptions setLevel(int level) {
this.level = level;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(String name) {
this.name = name;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(Pattern name) {
this.name = name;
return this;
}
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public GetByRoleOptions setPressed(boolean pressed) {
this.pressed = pressed;
return this;
}
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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
@@ -861,6 +1095,12 @@ public interface Frame {
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* 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.
@@ -900,6 +1140,15 @@ public interface Frame {
this.modifiers = modifiers;
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 HoverOptions 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.
@@ -2409,8 +2658,8 @@ public interface Frame {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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 +2688,8 @@ public interface Frame {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelector(String selector, String expression) {
return evalOnSelector(selector, expression, null);
@@ -2468,8 +2717,8 @@ public interface Frame {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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 +2743,8 @@ public interface Frame {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelectorAll(String selector, String expression) {
return evalOnSelectorAll(selector, expression, null);
@@ -2521,8 +2770,8 @@ public interface Frame {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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 +2804,8 @@ public interface Frame {
* bodyHandle.dispose();
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -2590,8 +2839,8 @@ public interface Frame {
* bodyHandle.dispose();
* }</pre>
*
* @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 expression 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 +2871,8 @@ public interface Frame {
* resultHandle.dispose();
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -2655,8 +2904,8 @@ public interface Frame {
* resultHandle.dispose();
* }</pre>
*
* @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 expression 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 +2982,7 @@ public interface Frame {
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = frame.frameLocator("#my-iframe").locator("text=Submit");
* Locator locator = frame.frameLocator("#my-iframe").getByText("Submit");
* locator.click();
* }</pre>
*
@@ -2759,6 +3008,301 @@ public interface Frame {
* @param name Attribute name to get the value for.
*/
String getAttribute(String selector, String name, GetAttributeOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(String text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(String text, GetByAltTextOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(Pattern text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(Pattern text, GetByAltTextOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(String text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(String text, GetByLabelOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(Pattern text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(Pattern text, GetByLabelOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(String text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(String text, GetByPlaceholderOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(Pattern text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options);
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
default Locator getByRole(AriaRole role) {
return getByRole(role, null);
}
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
Locator getByRole(AriaRole role, GetByRoleOptions options);
/**
* Locate element by the test id. By default, the {@code data-testid} attribute is used as a test id. Use {@link
* Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id attribute if necessary.
*
* @param testId Id to locate the element by.
*/
Locator getByTestId(String testId);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(String text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(String text, GetByTextOptions options);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(Pattern text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(Pattern text, GetByTextOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(String text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(String text, GetByTitleOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(Pattern text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect.
@@ -3016,9 +3560,11 @@ public interface Frame {
*/
boolean isVisible(String selector, IsVisibleOptions options);
/**
* The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
@@ -3029,9 +3575,11 @@ public interface Frame {
return locator(selector, null);
}
/**
* The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
@@ -3859,8 +4407,8 @@ public interface Frame {
* frame.waitForFunction("selector => !!document.querySelector(selector)", selector);
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
default JSHandle waitForFunction(String expression, Object arg) {
@@ -3893,8 +4441,8 @@ public interface Frame {
* frame.waitForFunction("selector => !!document.querySelector(selector)", selector);
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle waitForFunction(String expression) {
return waitForFunction(expression, null);
@@ -3926,8 +4474,8 @@ public interface Frame {
* frame.waitForFunction("selector => !!document.querySelector(selector)", selector);
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle waitForFunction(String expression, Object arg, WaitForFunctionOptions options);
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.regex.Pattern;
/**
@@ -23,7 +24,7 @@ import java.util.regex.Pattern;
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
* Locator locator = page.frameLocator("#my-frame").getByText("Submit");
* locator.click();
* }</pre>
*
@@ -33,10 +34,10 @@ import java.util.regex.Pattern;
* a given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* page.frame_locator(".result-frame").locator("button").click();
* page.frame_locator(".result-frame").getByRole("button").click();
*
* // Works because we explicitly tell locator to pick the first frame:
* page.frame_locator(".result-frame").first().locator("button").click();
* page.frame_locator(".result-frame").first().getByRole("button").click();
* }</pre>
*
* <p> **Converting Locator to FrameLocator**
@@ -48,6 +49,240 @@ import java.util.regex.Pattern;
* }</pre>
*/
public interface FrameLocator {
class GetByAltTextOptions {
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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 <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public Boolean expanded;
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public Integer level;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public Object name;
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public Boolean pressed;
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
public Boolean selected;
/**
* An attribute that is usually set by {@code aria-checked} or native {@code <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public GetByRoleOptions setChecked(boolean checked) {
this.checked = checked;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public GetByRoleOptions setDisabled(boolean disabled) {
this.disabled = disabled;
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public GetByRoleOptions setExpanded(boolean expanded) {
this.expanded = expanded;
return this;
}
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public GetByRoleOptions setLevel(int level) {
this.level = level;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(String name) {
this.name = name;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(Pattern name) {
this.name = name;
return this;
}
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public GetByRoleOptions setPressed(boolean pressed) {
this.pressed = pressed;
return this;
}
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByTitleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
@@ -104,12 +339,310 @@ public interface FrameLocator {
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(String text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(String text, GetByAltTextOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(Pattern text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(Pattern text, GetByAltTextOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(String text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(String text, GetByLabelOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(Pattern text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(Pattern text, GetByLabelOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(String text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(String text, GetByPlaceholderOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(Pattern text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options);
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
default Locator getByRole(AriaRole role) {
return getByRole(role, null);
}
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
Locator getByRole(AriaRole role, GetByRoleOptions options);
/**
* Locate element by the test id. By default, the {@code data-testid} attribute is used as a test id. Use {@link
* Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id attribute if necessary.
*
* @param testId Id to locate the element by.
*/
Locator getByTestId(String testId);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(String text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(String text, GetByTextOptions options);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(Pattern text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(Pattern text, GetByTextOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(String text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(String text, GetByTitleOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(Pattern text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Returns locator to the last matching frame.
*/
FrameLocator last();
/**
* The method finds an element matching the specified selector in the FrameLocator's subtree.
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
@@ -118,7 +651,10 @@ public interface FrameLocator {
return locator(selector, null);
}
/**
* The method finds an element matching the specified selector in the FrameLocator's subtree.
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
@@ -57,8 +57,8 @@ public interface JSHandle {
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -78,8 +78,8 @@ public interface JSHandle {
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String expression, Object arg);
@@ -97,8 +97,8 @@ public interface JSHandle {
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -117,8 +117,8 @@ public interface JSHandle {
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} 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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String expression, Object arg);
@@ -20,7 +20,7 @@ import com.microsoft.playwright.options.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
* which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page.
* which takes raw characters and generates proper {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} events on your page.
*
* <p> For finer control, you can use {@link Keyboard#down Keyboard.down()}, {@link Keyboard#up Keyboard.up()}, and {@link
* Keyboard#insertText Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
@@ -29,6 +29,24 @@ import java.util.regex.Pattern;
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*/
public interface Locator {
class BlurOptions {
/**
* 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;
/**
* 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 BlurOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class BoundingBoxOptions {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
@@ -128,6 +146,52 @@ public interface Locator {
return this;
}
}
class ClearOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> 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;
/**
* 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;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks. Defaults to
* {@code false}.
*/
public ClearOptions 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 ClearOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
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 ClearOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ClickOptions {
/**
* Defaults to {@code left}.
@@ -670,6 +734,240 @@ public interface Locator {
return this;
}
}
class GetByAltTextOptions {
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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 <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public Boolean expanded;
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public Integer level;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public Object name;
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public Boolean pressed;
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
public Boolean selected;
/**
* An attribute that is usually set by {@code aria-checked} or native {@code <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public GetByRoleOptions setChecked(boolean checked) {
this.checked = checked;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public GetByRoleOptions setDisabled(boolean disabled) {
this.disabled = disabled;
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public GetByRoleOptions setExpanded(boolean expanded) {
this.expanded = expanded;
return this;
}
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public GetByRoleOptions setLevel(int level) {
this.level = level;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(String name) {
this.name = name;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(Pattern name) {
this.name = name;
return this;
}
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public GetByRoleOptions setPressed(boolean pressed) {
this.pressed = pressed;
return this;
}
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByTitleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
}
class HoverOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks. Defaults to
@@ -681,6 +979,12 @@ public interface Locator {
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* 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.
@@ -715,6 +1019,15 @@ public interface Locator {
this.modifiers = modifiers;
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 HoverOptions 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.
@@ -1034,7 +1347,7 @@ public interface Locator {
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -1107,7 +1420,7 @@ public interface Locator {
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -1632,6 +1945,16 @@ public interface Locator {
* Returns an array of {@code node.textContent} values for all matching nodes.
*/
List<String> allTextContents();
/**
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
*/
default void blur() {
blur(null);
}
/**
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
*/
void blur(BlurOptions options);
/**
* This method returns the bounding box of the element, or {@code null} if the element is not visible. The bounding box is
* calculated relative to the main frame viewport - which is usually the same as the browser window.
@@ -1712,6 +2035,28 @@ public interface Locator {
* zero timeout disables this.
*/
void check(CheckOptions options);
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, focuses the
* element, clears it and triggers an {@code input} event after clearing.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be
* cleared instead.
*/
default void clear() {
clear(null);
}
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, focuses the
* element, clears it and triggers an {@code input} event after clearing.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be
* cleared instead.
*/
void clear(ClearOptions options);
/**
* This method clicks the element by performing the following steps:
* <ol>
@@ -1899,7 +2244,17 @@ public interface Locator {
*/
void dispatchEvent(String type, Object eventInit, DispatchEventOptions options);
/**
* This method drags the locator to another target locator or target position. It will first move to the source element,
* perform a {@code mousedown}, then move to the target element or position and perform a {@code mouseup}.
* <pre>{@code
* Locator source = page.locator("#source");
* Locator target = page.locator("#target");
*
* source.dragTo(target);
* // or specify exact positions relative to the top-left corners of the elements:
* source.dragTo(target, new Locator.DragToOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre>
*
* @param target Locator of the element to drag to.
*/
@@ -1907,7 +2262,17 @@ public interface Locator {
dragTo(target, null);
}
/**
* This method drags the locator to another target locator or target position. It will first move to the source element,
* perform a {@code mousedown}, then move to the target element or position and perform a {@code mouseup}.
* <pre>{@code
* Locator source = page.locator("#source");
* Locator target = page.locator("#target");
*
* source.dragTo(target);
* // or specify exact positions relative to the top-left corners of the elements:
* source.dragTo(target, new Locator.DragToOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre>
*
* @param target Locator of the element to drag to.
*/
@@ -1943,8 +2308,8 @@ public interface Locator {
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
default Object evaluate(String expression, Object arg) {
@@ -1965,8 +2330,8 @@ public interface Locator {
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -1986,8 +2351,8 @@ public interface Locator {
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String expression, Object arg, EvaluateOptions options);
@@ -2005,8 +2370,8 @@ public interface Locator {
* boolean divCounts = (boolean) elements.evaluateAll("(divs, min) => divs.length >= min", 10);
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluateAll(String expression) {
return evaluateAll(expression, null);
@@ -2025,8 +2390,8 @@ public interface Locator {
* boolean divCounts = (boolean) elements.evaluateAll("(divs, min) => divs.length >= min", 10);
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluateAll(String expression, Object arg);
@@ -2044,8 +2409,8 @@ public interface Locator {
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} 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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
default JSHandle evaluateHandle(String expression, Object arg) {
@@ -2065,8 +2430,8 @@ public interface Locator {
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -2085,8 +2450,8 @@ public interface Locator {
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} 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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options);
@@ -2131,7 +2496,7 @@ public interface Locator {
* rowLocator
* .filter(new Locator.FilterOptions().setHasText("text in column 1"))
* .filter(new Locator.FilterOptions().setHas(
* page.locator("button", new Page.LocatorOptions().setHasText("column 2 button"))
* page.getByRole("button", new Page.GetByRoleOptions().setName("column 2 button"))
* ))
* .screenshot();
* }</pre>
@@ -2148,7 +2513,7 @@ public interface Locator {
* rowLocator
* .filter(new Locator.FilterOptions().setHasText("text in column 1"))
* .filter(new Locator.FilterOptions().setHas(
* page.locator("button", new Page.LocatorOptions().setHasText("column 2 button"))
* page.getByRole("button", new Page.GetByRoleOptions().setName("column 2 button"))
* ))
* .screenshot();
* }</pre>
@@ -2172,7 +2537,7 @@ public interface Locator {
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe:
* <pre>{@code
* Locator locator = page.frameLocator("iframe").locator("text=Submit");
* Locator locator = page.frameLocator("iframe").getByText("Submit");
* locator.click();
* }</pre>
*
@@ -2194,6 +2559,301 @@ public interface Locator {
* @param name Attribute name to get the value for.
*/
String getAttribute(String name, GetAttributeOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(String text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(String text, GetByAltTextOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(Pattern text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(Pattern text, GetByAltTextOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(String text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(String text, GetByLabelOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(Pattern text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(Pattern text, GetByLabelOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(String text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(String text, GetByPlaceholderOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(Pattern text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options);
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
default Locator getByRole(AriaRole role) {
return getByRole(role, null);
}
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
Locator getByRole(AriaRole role, GetByRoleOptions options);
/**
* Locate element by the test id. By default, the {@code data-testid} attribute is used as a test id. Use {@link
* Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id attribute if necessary.
*
* @param testId Id to locate the element by.
*/
Locator getByTestId(String testId);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(String text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(String text, GetByTextOptions options);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(Pattern text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(Pattern text, GetByTextOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(String text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(String text, GetByTitleOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(Pattern text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses {@link
* Locator#highlight Locator.highlight()}.
@@ -2340,9 +3000,11 @@ public interface Locator {
*/
Locator last();
/**
* The method finds an element matching the specified selector in the {@code Locator}'s subtree. It also accepts filter options,
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -2350,9 +3012,11 @@ public interface Locator {
return locator(selector, null);
}
/**
* The method finds an element matching the specified selector in the {@code Locator}'s subtree. It also accepts filter options,
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -2992,8 +3656,8 @@ public interface Locator {
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.locator("input");
* element.type("some text");
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
*
@@ -3013,8 +3677,8 @@ public interface Locator {
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.locator("input");
* element.type("some text");
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
*
@@ -1142,6 +1142,240 @@ public interface Page extends AutoCloseable {
return this;
}
}
class GetByAltTextOptions {
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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 <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public Boolean expanded;
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public Integer level;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public Object name;
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public Boolean pressed;
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
public Boolean selected;
/**
* An attribute that is usually set by {@code aria-checked} or native {@code <input type=checkbox>} controls.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public GetByRoleOptions setChecked(boolean checked) {
this.checked = checked;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
* <p> <strong>NOTE:</strong> Unlike most other attributes, {@code disabled} is inherited through the DOM hierarchy. Learn more about <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-disabled">{@code aria-disabled}</a>.
*/
public GetByRoleOptions setDisabled(boolean disabled) {
this.disabled = disabled;
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
/**
* An attribute that is usually set by {@code aria-expanded}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-expanded">{@code aria-expanded}</a>.
*/
public GetByRoleOptions setExpanded(boolean expanded) {
this.expanded = expanded;
return this;
}
/**
* Option that controls whether hidden elements are matched. By default, only non-hidden elements, as <a
* href="https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion">defined by ARIA</a>, are matched by role selector.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-hidden">{@code aria-hidden}</a>.
*/
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 <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
public GetByRoleOptions setLevel(int level) {
this.level = level;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(String name) {
this.name = name;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. By default,
* matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*/
public GetByRoleOptions setName(Pattern name) {
this.name = name;
return this;
}
/**
* An attribute that is usually set by {@code aria-pressed}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
*/
public GetByRoleOptions setPressed(boolean pressed) {
this.pressed = pressed;
return this;
}
/**
* An attribute that is usually set by {@code aria-selected}.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-selected">{@code aria-selected}</a>.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
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. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
* expression. Note that exact match still trims whitespace.
*/
public GetByTitleOptions setExact(boolean exact) {
this.exact = exact;
return this;
}
}
class GoBackOptions {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
@@ -1295,6 +1529,12 @@ public interface Page extends AutoCloseable {
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* 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.
@@ -1334,6 +1574,15 @@ public interface Page extends AutoCloseable {
this.modifiers = modifiers;
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 HoverOptions 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.
@@ -2002,7 +2251,8 @@ public interface Page extends AutoCloseable {
*/
public HarNotFound notFound;
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when {@link BrowserContext#close BrowserContext.close()} is called.
*/
public Boolean update;
/**
@@ -2024,7 +2274,8 @@ public interface Page extends AutoCloseable {
return this;
}
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when {@link BrowserContext#close BrowserContext.close()} is called.
*/
public RouteFromHAROptions setUpdate(boolean update) {
this.update = update;
@@ -2095,7 +2346,7 @@ public interface Page extends AutoCloseable {
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -2189,7 +2440,7 @@ public interface Page extends AutoCloseable {
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenshots of
* high-dpi devices will be twice as large or even larger.
*
* <p> Defaults to {@code "device"}.
@@ -3576,7 +3827,14 @@ public interface Page extends AutoCloseable {
*/
void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options);
/**
*
* This method drags the source element to the target element. It will first move to the source element, perform a
* {@code mousedown}, then move to the target element and perform a {@code mouseup}.
* <pre>{@code
* page.dragAndDrop("#source", '#target');
* // or specify exact positions relative to the top-left corners of the elements:
* page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre>
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3587,7 +3845,14 @@ public interface Page extends AutoCloseable {
dragAndDrop(source, target, null);
}
/**
*
* This method drags the source element to the target element. It will first move to the source element, perform a
* {@code mousedown}, then move to the target element and perform a {@code mouseup}.
* <pre>{@code
* page.dragAndDrop("#source", '#target');
* // or specify exact positions relative to the top-left corners of the elements:
* page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre>
*
* @param source A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
* used. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3683,8 +3948,8 @@ public interface Page extends AutoCloseable {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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) {
@@ -3712,8 +3977,8 @@ public interface Page extends AutoCloseable {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelector(String selector, String expression) {
return evalOnSelector(selector, expression, null);
@@ -3740,8 +4005,8 @@ public interface Page extends AutoCloseable {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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);
@@ -3763,8 +4028,8 @@ public interface Page extends AutoCloseable {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evalOnSelectorAll(String selector, String expression) {
return evalOnSelectorAll(selector, expression, null);
@@ -3787,8 +4052,8 @@ public interface Page extends AutoCloseable {
*
* @param selector A selector to query for. See <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> 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 expression 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);
@@ -3825,8 +4090,8 @@ public interface Page extends AutoCloseable {
*
* <p> Shortcut for main frame's {@link Frame#evaluate Frame.evaluate()}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -3864,8 +4129,8 @@ public interface Page extends AutoCloseable {
*
* <p> Shortcut for main frame's {@link Frame#evaluate Frame.evaluate()}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String expression, Object arg);
@@ -3896,8 +4161,8 @@ public interface Page extends AutoCloseable {
* resultHandle.dispose();
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -3929,8 +4194,8 @@ public interface Page extends AutoCloseable {
* resultHandle.dispose();
* }</pre>
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String expression, Object arg);
@@ -4208,7 +4473,7 @@ public interface Page extends AutoCloseable {
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = page.frameLocator("#my-iframe").locator("text=Submit");
* Locator locator = page.frameLocator("#my-iframe").getByText("Submit");
* locator.click();
* }</pre>
*
@@ -4238,6 +4503,301 @@ public interface Page extends AutoCloseable {
* @param name Attribute name to get the value for.
*/
String getAttribute(String selector, String name, GetAttributeOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(String text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(String text, GetByAltTextOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
default Locator getByAltText(Pattern text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
*
* @param text Text to locate the element for.
*/
Locator getByAltText(Pattern text, GetByAltTextOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(String text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(String text, GetByLabelOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
default Locator getByLabel(Pattern text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
*
* @param text Text to locate the element for.
*/
Locator getByLabel(Pattern text, GetByLabelOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(String text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(String text, GetByPlaceholderOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
default Locator getByPlaceholder(Pattern text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
*
* @param text Text to locate the element for.
*/
Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options);
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
default Locator getByRole(AriaRole role) {
return getByRole(role, null);
}
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
*
* @param role Required aria role.
*/
Locator getByRole(AriaRole role, GetByRoleOptions options);
/**
* Locate element by the test id. By default, the {@code data-testid} attribute is used as a test id. Use {@link
* Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id attribute if necessary.
*
* @param testId Id to locate the element by.
*/
Locator getByTestId(String testId);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(String text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(String text, GetByTextOptions options);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
default Locator getByText(Pattern text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
*/
Locator getByText(Pattern text, GetByTextOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(String text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(String text, GetByTitleOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
default Locator getByTitle(Pattern text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
*
* @param text Text to locate the element for.
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect. If can not go back, returns {@code null}.
@@ -4540,14 +5100,12 @@ public interface Page extends AutoCloseable {
boolean isVisible(String selector, IsVisibleOptions options);
Keyboard keyboard();
/**
* The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <p> Shortcut for main frame's {@link Frame#locator Frame.locator()}.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -4555,14 +5113,12 @@ public interface Page extends AutoCloseable {
return locator(selector, null);
}
/**
* The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <p> Shortcut for main frame's {@link Frame#locator Frame.locator()}.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -6106,8 +6662,8 @@ public interface Page extends AutoCloseable {
*
* <p> Shortcut for main frame's {@link Frame#waitForFunction Frame.waitForFunction()}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
default JSHandle waitForFunction(String expression, Object arg) {
@@ -6142,8 +6698,8 @@ public interface Page extends AutoCloseable {
*
* <p> Shortcut for main frame's {@link Frame#waitForFunction Frame.waitForFunction()}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle waitForFunction(String expression) {
return waitForFunction(expression, null);
@@ -6177,8 +6733,8 @@ public interface Page extends AutoCloseable {
*
* <p> Shortcut for main frame's {@link Frame#waitForFunction Frame.waitForFunction()}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle waitForFunction(String expression, Object arg, WaitForFunctionOptions options);
@@ -6188,12 +6744,12 @@ public interface Page extends AutoCloseable {
* <p> This resolves when the page reaches a required load state, {@code load} by default. The navigation must have been committed
* when this method is called. If current document has already reached the required state, resolves immediately.
* <pre>{@code
* page.click("button"); // Click triggers navigation.
* page.getByRole("button").click(); // Click triggers navigation.
* page.waitForLoadState(); // The promise resolves after "load" event.
* }</pre>
* <pre>{@code
* Page popup = page.waitForPopup(() -> {
* page.click("button"); // Click triggers a popup.
* page.getByRole("button").click(); // Click triggers a popup.
* });
* popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
* System.out.println(popup.title()); // Popup is ready to use.
@@ -6218,12 +6774,12 @@ public interface Page extends AutoCloseable {
* <p> This resolves when the page reaches a required load state, {@code load} by default. The navigation must have been committed
* when this method is called. If current document has already reached the required state, resolves immediately.
* <pre>{@code
* page.click("button"); // Click triggers navigation.
* page.getByRole("button").click(); // Click triggers navigation.
* page.waitForLoadState(); // The promise resolves after "load" event.
* }</pre>
* <pre>{@code
* Page popup = page.waitForPopup(() -> {
* page.click("button"); // Click triggers a popup.
* page.getByRole("button").click(); // Click triggers a popup.
* });
* popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
* System.out.println(popup.title()); // Popup is ready to use.
@@ -6240,12 +6796,12 @@ public interface Page extends AutoCloseable {
* <p> This resolves when the page reaches a required load state, {@code load} by default. The navigation must have been committed
* when this method is called. If current document has already reached the required state, resolves immediately.
* <pre>{@code
* page.click("button"); // Click triggers navigation.
* page.getByRole("button").click(); // Click triggers navigation.
* page.waitForLoadState(); // The promise resolves after "load" event.
* }</pre>
* <pre>{@code
* Page popup = page.waitForPopup(() -> {
* page.click("button"); // Click triggers a popup.
* page.getByRole("button").click(); // Click triggers a popup.
* });
* popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
* System.out.println(popup.title()); // Popup is ready to use.
@@ -34,7 +34,7 @@ import java.util.*;
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@code "requestfinished"} event.
*
* <p> If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new
* <p> If request gets a 'redirect' response, the request is successfully finished with the {@code requestfinished} event, and a new
* request is issued to a redirected url.
*/
public interface Request {
@@ -177,5 +177,12 @@ public interface Selectors {
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
*/
void register(String name, Path script, RegisterOptions options);
/**
* Defines custom attribute name to be used in {@link Page#getByTestId Page.getByTestId()}. {@code data-testid} is used by
* default.
*
* @param attributeName Test id attribute name.
*/
void setTestIdAttribute(String attributeName);
}
@@ -192,7 +192,7 @@ public interface Tracing {
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=Get Started").click();
* page.getByText("Get Started").click();
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
@@ -219,7 +219,7 @@ public interface Tracing {
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=Get Started").click();
* page.getByText("Get Started").click();
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
@@ -71,8 +71,8 @@ public interface Worker {
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -88,8 +88,8 @@ public interface Worker {
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String expression, Object arg);
@@ -103,8 +103,8 @@ public interface Worker {
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -119,8 +119,8 @@ public interface Worker {
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
*
* @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 expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String expression, Object arg);
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* @Test
* void statusBecomesSubmitted() {
* ...
* page.locator("#submit-button").click();
* page.getByRole("button").click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
@@ -369,7 +369,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to a checked input.
* <pre>{@code
* assertThat(page.locator(".subscribe")).isChecked();
* assertThat(page.getByLabel("Subscribe to newsletter")).isChecked();
* }</pre>
*/
default void isChecked() {
@@ -378,7 +378,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to a checked input.
* <pre>{@code
* assertThat(page.locator(".subscribe")).isChecked();
* assertThat(page.getByLabel("Subscribe to newsletter")).isChecked();
* }</pre>
*/
void isChecked(IsCheckedOptions options);
@@ -409,7 +409,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an editable element.
* <pre>{@code
* assertThat(page.locator("input")).isEditable();
* assertThat(page.getByRole("textbox")).isEditable();
* }</pre>
*/
default void isEditable() {
@@ -418,7 +418,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an editable element.
* <pre>{@code
* assertThat(page.locator("input")).isEditable();
* assertThat(page.getByRole("textbox")).isEditable();
* }</pre>
*/
void isEditable(IsEditableOptions options);
@@ -457,7 +457,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to a focused DOM node.
* <pre>{@code
* assertThat(page.locator("input")).isFocused();
* assertThat(page.getByRole("textbox")).isFocused();
* }</pre>
*/
default void isFocused() {
@@ -466,7 +466,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to a focused DOM node.
* <pre>{@code
* assertThat(page.locator("input")).isFocused();
* assertThat(page.getByRole("textbox")).isFocused();
* }</pre>
*/
void isFocused(IsFocusedOptions options);
@@ -489,7 +489,7 @@ public interface LocatorAssertions {
*/
void isHidden(IsHiddenOptions options);
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/api/actionability#visible">attached</a>
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/api/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM node.
* <pre>{@code
* assertThat(page.locator(".my-element")).isVisible();
@@ -499,7 +499,7 @@ public interface LocatorAssertions {
isVisible(null);
}
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/api/actionability#visible">attached</a>
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/api/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM node.
* <pre>{@code
* assertThat(page.locator(".my-element")).isVisible();
@@ -997,7 +997,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
* <pre>{@code
* assertThat(page.locator("button")).hasCSS("display", "flex");
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
* }</pre>
*
* @param name CSS property name.
@@ -1009,7 +1009,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
* <pre>{@code
* assertThat(page.locator("button")).hasCSS("display", "flex");
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
* }</pre>
*
* @param name CSS property name.
@@ -1019,7 +1019,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
* <pre>{@code
* assertThat(page.locator("button")).hasCSS("display", "flex");
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
* }</pre>
*
* @param name CSS property name.
@@ -1031,7 +1031,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
* <pre>{@code
* assertThat(page.locator("button")).hasCSS("display", "flex");
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
* }</pre>
*
* @param name CSS property name.
@@ -1041,7 +1041,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
* <pre>{@code
* assertThat(page.locator("input")).hasId("lastname");
* assertThat(page.getByRole("textbox")).hasId("lastname");
* }</pre>
*
* @param id Element id.
@@ -1052,7 +1052,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
* <pre>{@code
* assertThat(page.locator("input")).hasId("lastname");
* assertThat(page.getByRole("textbox")).hasId("lastname");
* }</pre>
*
* @param id Element id.
@@ -1061,7 +1061,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
* <pre>{@code
* assertThat(page.locator("input")).hasId("lastname");
* assertThat(page.getByRole("textbox")).hasId("lastname");
* }</pre>
*
* @param id Element id.
@@ -1072,7 +1072,7 @@ public interface LocatorAssertions {
/**
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
* <pre>{@code
* assertThat(page.locator("input")).hasId("lastname");
* assertThat(page.getByRole("textbox")).hasId("lastname");
* }</pre>
*
* @param id Element id.
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* @Test
* void navigatesToLoginPage() {
* ...
* page.locator("#login").click();
* page.getByText("Sign in").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
@@ -26,7 +26,6 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.writeToFile;
class ArtifactImpl extends ChannelOwner {
boolean isRemote;
public ArtifactImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -57,7 +56,7 @@ class ArtifactImpl extends ChannelOwner {
}
public Path pathAfterFinished() {
if (isRemote) {
if (connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("pathAfterFinished").getAsJsonObject();
@@ -65,7 +64,7 @@ class ArtifactImpl extends ChannelOwner {
}
public void saveAs(Path path) {
if (isRemote) {
if (connection.isRemote) {
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
writeToFile(stream.stream(), path);
@@ -49,7 +49,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private boolean isClosedOrClosing;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.REQUEST, "request");
result.put(EventType.RESPONSE, "response");
result.put(EventType.REQUESTFINISHED, "requestFinished");
result.put(EventType.REQUESTFAILED, "requestFailed");
return result;
}
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
@@ -82,7 +90,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
browser = null;
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
}
@@ -201,12 +208,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params).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;
}
// Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue();
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
@@ -38,7 +38,6 @@ import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
BrowserTypeImpl browserType;
@@ -83,9 +83,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
headers.addProperty("x-playwright-browser", name());
}
JsonObject json = sendMessage("connect", params).getAsJsonObject();
JsonObject json = connection.localUtils().sendMessage("connect", params).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env);
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
@@ -97,7 +97,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isRemote = true;
browser.isConnectedOverWebSocket = true;
browser.browserType = this;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
@@ -132,7 +131,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
browser.browserType = this;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
@@ -55,6 +55,7 @@ public class Connection {
private final Transport transport;
private final Map<String, ChannelOwner> objects = new HashMap<>();
private final Root root;
final boolean isRemote;
private int lastId = 0;
private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
@@ -80,8 +81,18 @@ public class Connection {
}
}
Connection(Transport pipe, Map<String, String> env, LocalUtils localUtils) {
this(pipe, env, true);
this.localUtils = localUtils;
}
Connection(Transport transport, Map<String, String> env) {
this(transport, env, false);
}
private Connection(Transport transport, Map<String, String> env, boolean isRemote) {
this.env = env;
this.isRemote = isRemote;
if (isLogging) {
transport = new TransportLogger(transport);
}
@@ -283,8 +294,10 @@ public class Connection {
result = new JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
localUtils = new LocalUtils(parent, type, guid, initializer);
result = localUtils;
result = new LocalUtils(parent, type, guid, initializer);
if (localUtils == null) {
localUtils = (LocalUtils) result;
}
break;
case "Page":
result = new PageImpl(parent, type, guid, initializer);
@@ -31,6 +31,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.options.WaitUntilState.*;
import static com.microsoft.playwright.impl.Serialization.*;
@@ -373,6 +374,66 @@ public class FrameImpl extends ChannelOwner implements Frame {
return withLogging("Frame.getAttribute", () -> getAttributeImpl(selector, name, options));
}
@Override
public Locator getByAltText(String text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, convertType(options, Locator.GetByAltTextOptions.class)));
}
@Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, convertType(options, Locator.GetByAltTextOptions.class)));
}
@Override
public Locator getByLabel(String text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, convertType(options, Locator.GetByLabelOptions.class)));
}
@Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, convertType(options, Locator.GetByLabelOptions.class)));
}
@Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, convertType(options, Locator.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, convertType(options, Locator.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return locator(getByRoleSelector(role, convertType(options, Locator.GetByRoleOptions.class)));
}
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return locator(getByTextSelector(text, convertType(options, Locator.GetByTextOptions.class)));
}
@Override
public Locator getByText(Pattern text, GetByTextOptions options) {
return locator(getByTextSelector(text, convertType(options, Locator.GetByTextOptions.class)));
}
@Override
public Locator getByTitle(String text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, convertType(options, Locator.GetByTitleOptions.class)));
}
@Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, convertType(options, Locator.GetByTitleOptions.class)));
}
String getAttributeImpl(String selector, String name, GetAttributeOptions options) {
if (options == null) {
options = new GetAttributeOptions();
@@ -18,7 +18,11 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Utils.convertType;
class FrameLocatorImpl implements FrameLocator {
@@ -37,7 +41,67 @@ class FrameLocatorImpl implements FrameLocator {
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
return new FrameLocatorImpl(frame, frameSelector + " >> internal:control=enter-frame >> " + selector);
}
@Override
public Locator getByAltText(String text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, convertType(options, Locator.GetByAltTextOptions.class)));
}
@Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, convertType(options, Locator.GetByAltTextOptions.class)));
}
@Override
public Locator getByLabel(String text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, convertType(options, Locator.GetByLabelOptions.class)));
}
@Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, convertType(options, Locator.GetByLabelOptions.class)));
}
@Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, convertType(options, Locator.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, convertType(options, Locator.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return locator(getByRoleSelector(role, convertType(options, Locator.GetByRoleOptions.class)));
}
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return locator(getByTextSelector(text, convertType(options, Locator.GetByTextOptions.class)));
}
@Override
public Locator getByText(Pattern text, GetByTextOptions options) {
return locator(getByTextSelector(text, convertType(options, Locator.GetByTextOptions.class)));
}
@Override
public Locator getByTitle(String text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, convertType(options, Locator.GetByTitleOptions.class)));
}
@Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, convertType(options, Locator.GetByTitleOptions.class)));
}
@Override
@@ -47,7 +111,7 @@ class FrameLocatorImpl implements FrameLocator {
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
return new LocatorImpl(frame, frameSelector + " >> internal:control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
}
@Override
@@ -16,14 +16,23 @@
package com.microsoft.playwright.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import com.google.gson.JsonObject;
import java.util.*;
import java.util.function.Consumer;
class ListenerCollection <EventType> {
private final HashMap<EventType, List<Consumer<?>>> listeners = new HashMap<>();
private final Map<EventType, String> eventSubscriptions;
private final ChannelOwner channelOwner;
ListenerCollection() {
this(null, null);
}
ListenerCollection(Map<EventType, String> eventSubscriptions, ChannelOwner channelOwner) {
this.eventSubscriptions = eventSubscriptions;
this.channelOwner = channelOwner;
}
<T> void notify(EventType eventType, T param) {
List<Consumer<?>> list = listeners.get(eventType);
@@ -41,6 +50,7 @@ class ListenerCollection <EventType> {
if (list == null) {
list = new ArrayList<>();
listeners.put(type, list);
updateSubscription(type, true);
}
list.add(listener);
}
@@ -52,6 +62,7 @@ class ListenerCollection <EventType> {
}
list.removeAll(Collections.singleton(listener));
if (list.isEmpty()) {
updateSubscription(type, false);
listeners.remove(type);
}
}
@@ -59,4 +70,18 @@ class ListenerCollection <EventType> {
boolean hasListeners(EventType type) {
return listeners.containsKey(type);
}
private void updateSubscription(EventType eventType, boolean enabled) {
if (eventSubscriptions == null) {
return;
}
String protocolEvent = eventSubscriptions.get(eventType);
if (protocolEvent == null) {
return;
}
JsonObject params = new JsonObject();
params.addProperty("event", protocolEvent);
params.addProperty("enabled", enabled);
channelOwner.sendMessageAsync("updateSubscription", params);
}
}
@@ -3,10 +3,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import com.microsoft.playwright.options.WaitForSelectorState;
import com.microsoft.playwright.options.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
@@ -16,6 +13,7 @@ import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
@@ -24,57 +22,18 @@ class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
private static class Filters {
private final Map<Field, String> filterFieldToEngine = new LinkedHashMap<>();
private void addFilter(String name, String engine) throws NoSuchFieldException {
filterFieldToEngine.put(LocatorOptions.class.getField(name), engine);
}
{
try {
addFilter("has", "has");
// addFilter("leftOf", "left-of");
// addFilter("rightOf", "right-of");
// addFilter("above", "above");
// addFilter("below", "below");
// addFilter("near", "near");
} catch (NoSuchFieldException e) {
throw new InternalError(e);
}
}
String addFiltersToSelector(String selector, LocatorOptions options, Frame frame) {
try {
for (Map.Entry<Field, String> p : filterFieldToEngine.entrySet()) {
LocatorImpl filter = (LocatorImpl) p.getKey().get(options);
if (filter == null) {
continue;
}
if (filter.frame != frame) {
throw new PlaywrightException("Inner '" + p.getKey().getName() + "' locator must belong to the same frame.");
}
selector += " >> " + p.getValue() + "=" + gson().toJson(filter.selector);
}
} catch (IllegalAccessException e) {
throw new PlaywrightException("Unexpected options", e);
}
return selector;
}
}
private static final Filters filters = new Filters();
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
if (options != null) {
if (options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
String jsRegex = "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
selector += " >> has=" + gson().toJson("text=" + jsRegex);
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
}
selector += " >> internal:has-text=" + escapeForTextSelector(options.hasText, false);
}
if (options.has != null) {
LocatorImpl locator = (LocatorImpl) options.has;
if (locator.frame != frame)
throw new Error("Inner 'has' locator must belong to the same frame.");
selector += " >> internal:has=" + gson().toJson(locator.selector);
}
selector = filters.addFiltersToSelector(selector, options, frame);
}
this.selector = selector;
}
@@ -112,6 +71,21 @@ class LocatorImpl implements Locator {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.textContent || '')");
}
@Override
public void blur(BlurOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options));
}
private void blurImpl(BlurOptions options) {
if (options == null) {
options = new BlurOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("strict", true);
frame.sendMessage("blur", params);
}
@Override
public BoundingBox boundingBox(BoundingBoxOptions options) {
return withElement((h, o) -> h.boundingBox(), options);
@@ -125,6 +99,11 @@ class LocatorImpl implements Locator {
frame.check(selector, convertType(options, Frame.CheckOptions.class).setStrict(true));
}
@Override
public void clear(ClearOptions options) {
fill("", convertType(options, FillOptions.class));
}
@Override
public void click(ClickOptions options) {
if (options == null) {
@@ -234,6 +213,66 @@ class LocatorImpl implements Locator {
return frame.getAttribute(selector, name, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
}
@Override
public Locator getByAltText(String text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, options));
}
@Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return locator(getByAltTextSelector(text, options));
}
@Override
public Locator getByLabel(String text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, options));
}
@Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return locator(getByLabelSelector(text, options));
}
@Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, options));
}
@Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return locator(getByPlaceholderSelector(text, options));
}
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return locator(getByRoleSelector(role, options));
}
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return locator(getByTextSelector(text, options));
}
@Override
public Locator getByText(Pattern text, GetByTextOptions options) {
return locator(getByTextSelector(text, options));
}
@Override
public Locator getByTitle(String text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, options));
}
@Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return locator(getByTitleSelector(text, options));
}
@Override
public void highlight() {
frame.highlightImpl(selector);
@@ -0,0 +1,127 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
public class LocatorUtils {
private static volatile String testIdAttributeName = "data-testid";;
static void setTestIdAttributeName(String name) {
testIdAttributeName = name;
}
static String getByTextSelector(Object text, Locator.GetByTextOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return "internal:text=" + escapeForTextSelector(text, exact);
}
static String getByLabelSelector(Object text, Locator.GetByLabelOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return "internal:label=" + escapeForTextSelector(text, exact);
}
private static String getByAttributeTextSelector(String attrName, Object value, boolean exact) {
if (value instanceof Pattern) {
return "internal:attr=[" + attrName + "=" + toJsRegExp((Pattern) value) + "]";
}
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector((String) value, exact) + "]";
}
static String getByTestIdSelector(String testId) {
return getByAttributeTextSelector(testIdAttributeName, testId, true);
}
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return getByAttributeTextSelector("alt", text, exact);
}
static String getByTitleSelector(Object text, Locator.GetByTitleOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return getByAttributeTextSelector("title", text, exact);
}
static String getByPlaceholderSelector(Object text, Locator.GetByPlaceholderOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return getByAttributeTextSelector("placeholder", text, exact);
}
private static void addAttr(StringBuilder result, String name, String value) {
result.append("[").append(name).append("=").append(value).append("]");
}
static String getByRoleSelector(AriaRole role, Locator.GetByRoleOptions options) {
StringBuilder result = new StringBuilder();
result.append("internal:role=").append(role.name().toLowerCase());
if (options != null) {
if (options.checked != null)
addAttr(result, "checked", options.checked.toString());
if (options.disabled != null)
addAttr(result, "disabled", options.disabled.toString());
if (options.selected != null)
addAttr(result, "selected", options.selected.toString());
if (options.expanded != null)
addAttr(result, "expanded", options.expanded.toString());
if (options.includeHidden != null)
addAttr(result, "include-hidden", options.includeHidden.toString());
if (options.level != null)
addAttr(result, "level", options.level.toString());
if (options.name != null) {
String name;
if (options.name instanceof String) {
name = escapeForAttributeSelector((String) options.name, options.exact != null && options.exact);
} else if (options.name instanceof Pattern) {
name = toJsRegExp((Pattern) options.name);
} else {
throw new IllegalArgumentException("options.name can be String or Pattern, found: " + options.name);
}
addAttr(result, "name", name);
}
if (options.pressed != null)
addAttr(result, "pressed", options.pressed.toString());
}
return result.toString();
}
static String escapeForTextSelector(Object text, boolean exact) {
return escapeForTextSelector(text, exact, false);
}
private static String escapeForTextSelector(Object param, boolean exact, boolean caseSensitive) {
if (param instanceof Pattern) {
return toJsRegExp((Pattern) param);
}
if (!(param instanceof String)) {
throw new IllegalArgumentException("text parameter must be Pattern or String: " + param);
}
String text = (String) param;
if (exact) {
return '"' + text.replace("\"", "\\\"") + '"';
}
if (text.contains("\"") || text.contains(">>") || text.startsWith("/")) {
return "/" + escapeForRegex(text).replaceAll("\\s+", "\\\\s+") + "/" + (caseSensitive ? "" : "i");
}
return text;
}
private static String escapeForRegex(String text) {
return text.replaceAll("[.*+?^>${}()|\\[\\]\\\\]", "\\\\\\\\$0");
}
private static String escapeForAttributeSelector(String value, boolean exact) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + value.replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
}
private static String toJsRegExp(Pattern pattern) {
return "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
}
}
@@ -48,23 +48,16 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
@Override
void add(EventType eventType, Consumer<?> listener) {
if (eventType == EventType.FILECHOOSER) {
willAddFileChooserListener();
}
super.add(eventType, listener);
}
@Override
void remove(EventType eventType, Consumer<?> listener) {
super.remove(eventType, listener);
if (eventType == EventType.FILECHOOSER) {
didRemoveFileChooserListener();
}
}
};
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.REQUEST, "request");
result.put(EventType.RESPONSE, "response");
result.put(EventType.REQUESTFINISHED, "requestFinished");
result.put(EventType.REQUESTFAILED, "requestFailed");
result.put(EventType.FILECHOOSER, "fileChooser");
return result;
}
final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>(eventSubscriptions(), this);
final Map<String, BindingCallback> bindings = new HashMap<>();
BrowserContextImpl ownedContext;
private boolean isClosed;
@@ -151,7 +144,6 @@ public class PageImpl extends ChannelOwner implements Page {
} else if ("download".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
artifact.isRemote = browserContext.browser() != null && browserContext.browser().isRemote;
DownloadImpl download = new DownloadImpl(this, artifact, params);
listeners.notify(EventType.DOWNLOAD, download);
} else if ("fileChooser".equals(event)) {
@@ -233,24 +225,6 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.CLOSE, this);
}
private void willAddFileChooserListener() {
if (!listeners.hasListeners(EventType.FILECHOOSER)) {
updateFileChooserInterception(true);
}
}
private void didRemoveFileChooserListener() {
if (!listeners.hasListeners(EventType.FILECHOOSER)) {
updateFileChooserInterception(false);
}
}
private void updateFileChooserInterception(boolean enabled) {
JsonObject params = new JsonObject();
params.addProperty("intercepted", enabled);
sendMessage("setFileChooserInterceptedNoReply", params);
}
@Override
public void onClose(Consumer<Page> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -761,6 +735,77 @@ public class PageImpl extends ChannelOwner implements Page {
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
}
@Override
public Locator getByAltText(String text, GetByAltTextOptions options) {
return withLogging("Page.getAttribute",
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
}
@Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return withLogging("Page.getByAltText",
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
}
@Override
public Locator getByLabel(String text, GetByLabelOptions options) {
return withLogging("Page.getByLabel",
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
}
@Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return withLogging("Page.getByLabel",
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
}
@Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return withLogging("Page.getByPlaceholder",
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return withLogging("Page.getByPlaceholder",
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return withLogging("Page.getByRole",
() -> mainFrame.getByRole(role, convertType(options, Frame.GetByRoleOptions.class)));
}
@Override
public Locator getByTestId(String testId) {
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return withLogging("Page.getByText",
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
}
@Override
public Locator getByText(Pattern text, GetByTextOptions options) {
return withLogging("Page.getByText",
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
}
@Override
public Locator getByTitle(String text, GetByTitleOptions options) {
return withLogging("Page.getByTitle",
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
}
@Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return withLogging("Page.getByTitle",
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
}
@Override
public Response goBack(GoBackOptions options) {
return withLogging("Page.goBack", () -> goBackImpl(options));
@@ -378,7 +378,7 @@ class Serialization {
public JsonElement serialize(Optional<?> src, Type typeOfSrc, JsonSerializationContext context) {
assert isSupported(typeOfSrc) : "Unexpected optional type: " + typeOfSrc.getTypeName();
if (!src.isPresent()) {
return new JsonPrimitive("null");
return new JsonPrimitive("no-override");
}
return context.serialize(src.get());
}
@@ -25,6 +25,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.LocatorUtils.setTestIdAttributeName;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SharedSelectors extends LoggingSupport implements Selectors {
@@ -61,6 +62,12 @@ public class SharedSelectors extends LoggingSupport implements Selectors {
});
}
@Override
public void setTestIdAttribute(String attributeName) {
// TODO: set it per playwright insttance
setTestIdAttributeName(attributeName);
}
void addChannel(SelectorsImpl channel) {
registrations.forEach(r -> channel.registerImpl(r.name, r.script, r.options));
channels.add(channel);
@@ -69,7 +69,13 @@ class StackTraceCollector {
if (file == null) {
return "";
}
return resolveSourcePath(Paths.get(pkg).resolve(file));
try {
// The file name can contain an arbitrary string which may cause Path implementation
// to throw. See https://github.com/microsoft/playwright-java/issues/1115
return resolveSourcePath(Paths.get(pkg).resolve(file));
} catch (RuntimeException e) {
return "";
}
}
private String resolveSourcePath(Path relativePath) {
@@ -26,8 +26,6 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl extends ChannelOwner implements Tracing {
boolean isRemote;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -36,7 +34,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
JsonObject params = new JsonObject();
String mode = "doNotSave";
if (path != null) {
if (isRemote) {
if (connection.isRemote) {
mode = "compressTrace";
} else {
mode = "compressTraceAndSources";
@@ -48,16 +46,13 @@ class TracingImpl extends ChannelOwner implements Tracing {
return;
}
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 (isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(path);
artifact.delete();
// Add local sources to the remote trace if necessary.
if (isRemote && json.has("sourceEntries")) {
// In case of CDP connection since the connection is established by
// the driver it is safe to consider the artifact local.
if (connection.isRemote && json.has("sourceEntries")) {
JsonArray entries = json.getAsJsonArray("sourceEntries");
connection.localUtils.zip(path, entries);
}
@@ -165,7 +165,7 @@ class Utils {
}
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (context.browser().isRemote) {
if (context.connection.isRemote) {
List<WritableStream> streams = new ArrayList<>();
JsonArray jsonStreams = new JsonArray();
for (Path path : files) {
@@ -27,16 +27,13 @@ import static java.util.Arrays.asList;
class VideoImpl implements Video {
private final PageImpl page;
private final WaitableResult<ArtifactImpl> waitableArtifact = new WaitableResult<>();
private final boolean isRemote;
VideoImpl(PageImpl page) {
this.page = page;
BrowserImpl browser = page.context().browser();
isRemote = browser != null && browser.isRemote;
}
void setArtifact(ArtifactImpl artifact) {
artifact.isRemote = isRemote;
waitableArtifact.complete(artifact);
}
@@ -58,7 +55,7 @@ class VideoImpl implements Video {
@Override
public Path path() {
return page.withLogging("Video.path", () -> {
if (isRemote) {
if (page.connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
}
try {
@@ -0,0 +1,102 @@
/*
* 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 AriaRole {
ALERT,
ALERTDIALOG,
APPLICATION,
ARTICLE,
BANNER,
BLOCKQUOTE,
BUTTON,
CAPTION,
CELL,
CHECKBOX,
CODE,
COLUMNHEADER,
COMBOBOX,
COMPLEMENTARY,
CONTENTINFO,
DEFINITION,
DELETION,
DIALOG,
DIRECTORY,
DOCUMENT,
EMPHASIS,
FEED,
FIGURE,
FORM,
GENERIC,
GRID,
GRIDCELL,
GROUP,
HEADING,
IMG,
INSERTION,
LINK,
LIST,
LISTBOX,
LISTITEM,
LOG,
MAIN,
MARQUEE,
MATH,
METER,
MENU,
MENUBAR,
MENUITEM,
MENUITEMCHECKBOX,
MENUITEMRADIO,
NAVIGATION,
NONE,
NOTE,
OPTION,
PARAGRAPH,
PRESENTATION,
PROGRESSBAR,
RADIO,
RADIOGROUP,
REGION,
ROW,
ROWGROUP,
ROWHEADER,
SCROLLBAR,
SEARCH,
SEARCHBOX,
SEPARATOR,
SLIDER,
SPINBUTTON,
STATUS,
STRONG,
SUBSCRIPT,
SUPERSCRIPT,
SWITCH,
TAB,
TABLE,
TABLIST,
TABPANEL,
TERM,
TEXTBOX,
TIME,
TIMER,
TOOLBAR,
TOOLTIP,
TREE,
TREEGRID,
TREEITEM
}
@@ -287,6 +287,27 @@ public class TestBrowserContextFetch extends TestBase {
assertEquals("/simple.json", request.get().url);
}
@Test
void getShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().get(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("GET", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void headShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().head(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("HEAD", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void shouldAddDefaultHeaders() throws ExecutionException, InterruptedException {
@@ -2,13 +2,19 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.Geolocation;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@@ -22,6 +28,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
private BrowserContext persistentContext;
@TempDir Path tempDir;
@AfterEach
private void closePersistentContext() {
@@ -36,12 +43,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
}
private Page launchPersistent(BrowserType.LaunchPersistentContextOptions options) {
Path userDataDir = null;
try {
userDataDir = Files.createTempDirectory("user-data-dir-");
} catch (IOException e) {
throw new RuntimeException(e);
}
Path userDataDir = tempDir.resolve("user-data-dir");
assertNull(persistentContext);
persistentContext = browserType.launchPersistentContext(userDataDir, options);
return persistentContext.pages().get(0);
@@ -118,7 +120,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
@Test
void shouldAcceptUserDataDir() throws IOException {
// TODO: test.flaky(browserName === "chromium");
Path userDataDir = Files.createTempDirectory("user-data-dir-");
Path userDataDir = tempDir.resolve("user-data-dir");
BrowserContext context = browserType.launchPersistentContext(userDataDir);
assertTrue(userDataDir.toFile().listFiles().length > 0);
context.close();
@@ -128,7 +130,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
@Test
void shouldRestoreStateFromUserDataDir() throws IOException {
// TODO: test.slow();
Path userDataDir = Files.createTempDirectory("user-data-dir-");
Path userDataDir = tempDir.resolve("user-data-dir");
BrowserType.LaunchPersistentContextOptions browserOptions = null;
BrowserContext browserContext = browserType.launchPersistentContext(userDataDir, browserOptions);
Page page = browserContext.newPage();
@@ -142,7 +144,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
assertEquals("hello", page2.evaluate("localStorage.hey"));
browserContext2.close();
Path userDataDir2 = Files.createTempDirectory("user-data-dir-");
Path userDataDir2 = tempDir.resolve("user-data-dir-2");
BrowserContext browserContext3 = browserType.launchPersistentContext(userDataDir2, browserOptions);
Page page3 = browserContext3.newPage();
page3.navigate(server.EMPTY_PAGE);
@@ -153,7 +155,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
@Test
void shouldRestoreCookiesFromUserDataDir() throws IOException {
// TODO: test.flaky(browserName === "chromium");
Path userDataDir = Files.createTempDirectory("user-data-dir-");
Path userDataDir = tempDir.resolve("user-data-dir");
BrowserType.LaunchPersistentContextOptions browserOptions = null;
BrowserContext browserContext = browserType.launchPersistentContext(userDataDir, browserOptions);
Page page = browserContext.newPage();
@@ -171,7 +173,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
assertEquals("doSomethingOnlyOnce=true", page2.evaluate("() => document.cookie"));
browserContext2.close();
Path userDataDir2 = Files.createTempDirectory("user-data-dir-");
Path userDataDir2 = tempDir.resolve("user-data-dir-2");
BrowserContext browserContext3 = browserType.launchPersistentContext(userDataDir2, browserOptions);
Page page3 = browserContext3.newPage();
page3.navigate(server.EMPTY_PAGE);
@@ -191,7 +193,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
void shouldThrowIfPageArgumentIsPassed() throws IOException {
BrowserType.LaunchPersistentContextOptions options = new BrowserType.LaunchPersistentContextOptions()
.setArgs(asList(server.EMPTY_PAGE));
Path userDataDir = Files.createTempDirectory("user-data-dir-");
Path userDataDir = tempDir.resolve("user-data-dir");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
browserType.launchPersistentContext(userDataDir, options);
});
@@ -254,4 +256,43 @@ public class TestDefaultBrowserContext2 extends TestBase {
assertEquals("hello", page.innerHTML("defaultContextCSS=div"));
}
}
@Test
void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
Page page = launchPersistent();
page.navigate(server.PREFIX + "/input/fileupload.html");
Path uploadFile = tmpDir.resolve("200MB.zip");
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
for (int i = 0; i < 50 * 1024; i++) {
stream.write(str);
}
}
Locator input = page.locator("input[type='file']");
JSHandle events = input.evaluateHandle("e => {\n" +
" const events = [];\n" +
" e.addEventListener('input', () => events.push('input'));\n" +
" e.addEventListener('change', () => events.push('change'));\n" +
" return events;\n" +
" }");
input.setInputFiles(uploadFile);
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
assertEquals(asList("input", "change"), events.evaluate("e => e"));
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
server.setRoute("/upload", exchange -> {
try {
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
formData.complete(multipartFormData);
} catch (Exception e) {
e.printStackTrace();
formData.completeExceptionally(e);
}
exchange.sendResponseHeaders(200, -1);
});
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
List<MultipartFormData.Field> fields = formData.get().fields;
assertEquals(1, fields.size());
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}}
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.AriaRole;
import org.junit.jupiter.api.Test;
import java.net.MalformedURLException;
@@ -30,13 +31,14 @@ public class TestLocatorFrame extends TestBase {
.setBody("<iframe src='iframe.html'></iframe>").setContentType("text/html")));
page.route("**/iframe.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html>\n" +
" <div>\n" +
" <button>Hello iframe</button>\n" +
" <iframe src='iframe-2.html'></iframe>\n" +
" </div>\n" +
" <span>1</span>\n" +
" <span>2</span>\n" +
" </html>").setContentType("text/html"));
" <div>\n" +
" <button data-testid=\"buttonId\">Hello iframe</button>\n" +
" <iframe src=\"iframe-2.html\"></iframe>\n" +
" </div>\n" +
" <span>1</span>\n" +
" <span>2</span>\n" +
" <label for=target>Name</label><input id=target type=text placeholder=Placeholder title=Title alt=Alternative>\n" +
"</html>").setContentType("text/html"));
});
page.route("**/iframe-2.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello nested iframe</button></html>").setContentType("text/html"));
@@ -102,7 +104,7 @@ public class TestLocatorFrame extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300));
});
assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage());
assertTrue(e.getMessage().contains("waiting for frameLocator(\"iframe\")"), e.getMessage());
}
@Test
@@ -226,7 +228,7 @@ public class TestLocatorFrame extends TestBase {
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> button.waitFor());
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
assertTrue(e.getMessage().contains("Error: strict mode violation: locator(\"body\").locator(\"iframe\") resolved to 3 elements"), e.getMessage());
}
@Test
@@ -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("");
}
}
@@ -66,4 +66,40 @@ public class TestLocatorMisc extends TestBase{
assertTrue(e.getMessage().contains("Драматург"), e.getMessage());
}
}
@Test
void shouldClearInput() {
page.navigate(server.PREFIX + "/input/textarea.html");
Locator handle = page.locator("input");
handle.fill("some value");
assertEquals("some value", page.evaluate("() => window['result']"));
handle.clear();
assertEquals("", page.evaluate("() => window['result']"));
}
@Test
void shouldFocusAndBlurAButton() {
page.navigate(server.PREFIX + "/input/button.html");
Locator button = page.locator("button");
assertEquals(false, button.evaluate("button => document.activeElement === button"));
boolean[] focused = {false};
boolean[] blurred = {false};
page.exposeFunction("focusEvent", e -> focused[0] = true);
page.exposeFunction("blurEvent", e -> blurred[0] = true);
button.evaluate("button => {\n" +
" button.addEventListener('focus', window['focusEvent']);\n" +
" button.addEventListener('blur', window['blurEvent']);\n" +
" }");
button.focus();
assertTrue(focused[0]);
assertFalse(blurred[0]);
assertEquals(true, button.evaluate("button => document.activeElement === button"));
button.blur();
assertTrue(focused[0]);
assertTrue(blurred[0]);
assertEquals(false, button.evaluate("button => document.activeElement === button"));
}
}
@@ -243,4 +243,13 @@ public class TestPageFill extends TestBase {
page.fill("input", "");
assertEquals("", page.inputValue("input"));
}
@Test
void shouldBeAbleToClearUsingFill() {
page.navigate(server.PREFIX + "/input/textarea.html");
page.fill("input", "some value");
assertEquals("some value", page.evaluate("() => window['result']"));
page.fill("input", "");
assertEquals("", page.evaluate("() => window['result']"));
}
}
@@ -142,8 +142,16 @@ public class TestPopup extends TestBase {
page.navigate(server.EMPTY_PAGE);
Object[] size = {null};
Page popup = page.waitForPopup(() -> {
size[0] = page.evaluate("() => {\n" +
size[0] = page.evaluate("async () => {\n" +
" const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');\n" +
" await new Promise(resolve => {\n" +
" const interval = setInterval(() => {\n" +
" if (win.innerWidth === 600 && win.innerHeight === 300) {\n" +
" clearInterval(interval);\n" +
" resolve();\n" +
" }\n" +
" }, 10);\n" +
" });\n" +
" return { width: win.innerWidth, height: win.innerHeight };\n" +
"}");
});
@@ -0,0 +1,182 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.AriaRole;
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 java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestSelectorsGetBy extends TestBase {
@Test
void getByTestIdShouldWork() {
page.setContent("<div><div data-testid='Hello'>Hello world</div></div>");
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("<div><div data-testid='He\"llo'>Hello world</div></div>");
assertThat(page.getByTestId("He\"llo")).hasText("Hello world");
}
@Test
void getByTextShouldWork() {
page.setContent("<div>yo</div><div>ya</div><div>\nye </div>");
assertTrue(((String) page.getByText("ye").evaluate("e => e.outerHTML")).contains(">\nye </div>"));
assertTrue(((String) page.getByText(Pattern.compile("ye")).evaluate("e => e.outerHTML")).contains(">\nye </div>"));
assertTrue(((String) page.getByText(Pattern.compile("e")).evaluate("e => e.outerHTML")).contains(">\nye </div>"));
page.setContent("<div> ye </div><div>ye</div>");
assertTrue(((String) page.getByText("ye", new Page.GetByTextOptions().setExact(true)).first().evaluate("e => e.outerHTML")).contains("> ye </div>"));
page.setContent("<div>Hello world</div><div>Hello</div>");
assertEquals("<div>Hello</div>", page.getByText("Hello", new Page.GetByTextOptions().setExact(true)).evaluate("e => e.outerHTML"));
}
@Test
void getByLabelShouldWork() {
page.setContent("<div><label for=target>Name</label><input id=target type=text></div>");
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("<label for=target>Last <span>Name</span></label><input id=target type=text>");
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("<div>\n" +
" <input placeholder='Hello'>\n" +
" <input placeholder='Hello World'>\n" +
" </div>");
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("<div>\n" +
" <input alt='Hello'>\n" +
" <input alt='Hello World'>\n" +
" </div>");
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("<div>\n" +
" <input title='Hello'>\n" +
" <input title='Hello World'>\n" +
" </div>");
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("<label id=label for=control>Hello my\n" +
"wo\"rld</label><input id=control />");
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("<label id=label for=control>Hello my\n" +
"world</label><input id=control />");
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");
}
@Test
void getByRoleEscaping() {
page.setContent("<a href=\"https://playwright.dev\">issues 123</a>\n" +
" <a href=\"https://playwright.dev\">he llo 56</a>\n" +
" <button>Click me</button>");
assertEquals(
asList("<button>Click me</button>"),
page.getByRole(AriaRole.BUTTON).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList("<a href=\"https://playwright.dev\">issues 123</a>", "<a href=\"https://playwright.dev\">he llo 56</a>"),
page.getByRole(AriaRole.LINK).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList("<a href=\"https://playwright.dev\">issues 123</a>"),
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("issues 123")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList("<a href=\"https://playwright.dev\">issues 123</a>"),
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("sues")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList("<a href=\"https://playwright.dev\">he llo 56</a>"),
page.getByRole( AriaRole.LINK, new Page.GetByRoleOptions().setName(" he \n llo ")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList(),
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("issues")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList(),
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("sues").setExact(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(
asList("<a href=\"https://playwright.dev\">he llo 56</a>"),
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName(" he \n llo 56 ").setExact(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void locatorGetByRole() {
page.setContent("<div><button>Click me</button></div>");
assertEquals(
asList("<button>Click me</button>"),
page.locator("div").getByRole(AriaRole.BUTTON).evaluateAll("els => els.map(e => e.outerHTML)"));
}
}
@@ -0,0 +1,397 @@
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("<button>Hello</button>\n" +
" <select multiple=\"\" size=\"2\"></select>\n" +
" <select></select>\n" +
" <h3>Heading</h3>\n" +
" <details><summary>Hello</summary></details>\n" +
" <div role='dialog'>I am a dialog</div>");
assertEquals(asList("<button>Hello</button>"), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<select multiple=\"\" size=\"2\"></select>"), page.locator("role=listbox").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<select></select>"), page.locator("role=combobox").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<h3>Heading</h3>"), page.locator("role=heading").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<details><summary>Hello</summary></details>"), page.locator("role=group").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"dialog\">I am a dialog</div>"), 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("<select>\n" +
" <option>Hi</option>\n" +
" <option selected>Hello</option>\n" +
" </select>\n" +
" <div>\n" +
" <div role=\"option\" aria-selected=\"true\">Hi</div>\n" +
" <div role=\"option\" aria-selected=\"false\">Hello</div>\n" +
" </div>");
assertEquals(asList(
"<option selected=\"\">Hello</option>",
"<div role=\"option\" aria-selected=\"true\">Hi</div>"
), page.locator("role=option[selected]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<option selected=\"\">Hello</option>",
"<div role=\"option\" aria-selected=\"true\">Hi</div>"
), page.locator("role=option[selected=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<option selected=\"\">Hello</option>",
"<div role=\"option\" aria-selected=\"true\">Hi</div>"
), page.getByRole(AriaRole.OPTION, new Page.GetByRoleOptions().setSelected(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<option>Hi</option>",
"<div role=\"option\" aria-selected=\"false\">Hello</div>"
), page.locator("role=option[selected=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<option>Hi</option>",
"<div role=\"option\" aria-selected=\"false\">Hello</div>"
), page.getByRole(AriaRole.OPTION, new Page.GetByRoleOptions().setSelected(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportChecked() {
page.setContent("<input type=checkbox>\n" +
" <input type=checkbox checked>\n" +
" <input type=checkbox indeterminate>\n" +
" <div role=checkbox aria-checked=\"true\">Hi</div>\n" +
" <div role=checkbox aria-checked=\"false\">Hello</div>\n" +
" <div role=checkbox>Unknown</div>");
page.evalOnSelector("[indeterminate]", "input => input.indeterminate = true");
assertEquals(asList(
"<input type=\"checkbox\" checked=\"\">",
"<div role=\"checkbox\" aria-checked=\"true\">Hi</div>"
), page.locator("role=checkbox[checked]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\" checked=\"\">",
"<div role=\"checkbox\" aria-checked=\"true\">Hi</div>"
), page.locator("role=checkbox[checked=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\" checked=\"\">",
"<div role=\"checkbox\" aria-checked=\"true\">Hi</div>"
), page.getByRole(AriaRole.CHECKBOX, new Page.GetByRoleOptions().setChecked(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\">",
"<div role=\"checkbox\" aria-checked=\"false\">Hello</div>",
"<div role=\"checkbox\">Unknown</div>"
), page.locator("role=checkbox[checked=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\">",
"<div role=\"checkbox\" aria-checked=\"false\">Hello</div>",
"<div role=\"checkbox\">Unknown</div>"
), page.getByRole(AriaRole.CHECKBOX, new Page.GetByRoleOptions().setChecked(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\" indeterminate=\"\">"
), page.locator("role=checkbox[checked='mixed']").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<input type=\"checkbox\">",
"<input type=\"checkbox\" checked=\"\">",
"<input type=\"checkbox\" indeterminate=\"\">",
"<div role=\"checkbox\" aria-checked=\"true\">Hi</div>",
"<div role=\"checkbox\" aria-checked=\"false\">Hello</div>",
"<div role=\"checkbox\">Unknown</div>"
), page.locator("role=checkbox").evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportPressed() {
page.setContent("<button>Hi</button>\n" +
" <button aria-pressed=\"true\">Hello</button>\n" +
" <button aria-pressed=\"false\">Bye</button>\n" +
" <button aria-pressed=\"mixed\">Mixed</button>");
assertEquals(asList(
"<button aria-pressed=\"true\">Hello</button>"
), page.locator("role=button[pressed]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button aria-pressed=\"true\">Hello</button>"
), page.locator("role=button[pressed=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button aria-pressed=\"true\">Hello</button>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setPressed(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-pressed=\"false\">Bye</button>"
), page.locator("role=button[pressed=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-pressed=\"false\">Bye</button>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setPressed(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button aria-pressed=\"mixed\">Mixed</button>"
), page.locator("role=button[pressed='mixed']").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-pressed=\"true\">Hello</button>",
"<button aria-pressed=\"false\">Bye</button>",
"<button aria-pressed=\"mixed\">Mixed</button>"
), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportExpanded() {
page.setContent("<div role=\"treeitem\">Hi</div>\n" +
" <div role=\"treeitem\" aria-expanded=\"true\">Hello</div>\n" +
" <div role=\"treeitem\" aria-expanded=\"false\">Bye</div>");
assertEquals(asList(
"<div role=\"treeitem\">Hi</div>",
"<div role=\"treeitem\" aria-expanded=\"true\">Hello</div>",
"<div role=\"treeitem\" aria-expanded=\"false\">Bye</div>"
), page.locator("role=treeitem").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"treeitem\">Hi</div>",
"<div role=\"treeitem\" aria-expanded=\"true\">Hello</div>",
"<div role=\"treeitem\" aria-expanded=\"false\">Bye</div>"
), page.getByRole(AriaRole.TREEITEM).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"treeitem\" aria-expanded=\"true\">Hello</div>"),
page.locator("role=treeitem[expanded]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"treeitem\" aria-expanded=\"true\">Hello</div>"),
page.locator("role=treeitem[expanded=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"treeitem\" aria-expanded=\"true\">Hello</div>"),
page.getByRole(AriaRole.TREEITEM, new Page.GetByRoleOptions().setExpanded(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"treeitem\" aria-expanded=\"false\">Bye</div>"),
page.locator("role=treeitem[expanded=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList("<div role=\"treeitem\" aria-expanded=\"false\">Bye</div>"),
page.getByRole(AriaRole.TREEITEM, new Page.GetByRoleOptions().setExpanded(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
// Workaround for expanded='none'.
assertEquals(asList("<div role=\"treeitem\">Hi</div>"),
page.locator("[role=treeitem]:not([aria-expanded])").evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportDisabled() {
page.setContent("<button>Hi</button>\n" +
" <button disabled>Bye</button>\n" +
" <button aria-disabled=\"true\">Hello</button>\n" +
" <button aria-disabled=\"false\">Oh</button>\n" +
" <fieldset disabled>\n" +
" <button>Yay</button>\n" +
" </fieldset>");
assertEquals(asList(
"<button disabled=\"\">Bye</button>",
"<button aria-disabled=\"true\">Hello</button>",
"<button>Yay</button>"
), page.locator("role=button[disabled]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button disabled=\"\">Bye</button>",
"<button aria-disabled=\"true\">Hello</button>",
"<button>Yay</button>"
), page.locator("role=button[disabled=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button disabled=\"\">Bye</button>",
"<button aria-disabled=\"true\">Hello</button>",
"<button>Yay</button>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setDisabled(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-disabled=\"false\">Oh</button>"
), page.locator("role=button[disabled=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-disabled=\"false\">Oh</button>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setDisabled(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportLevel() {
page.setContent("<h1>Hello</h1>\n" +
" <h3>Hi</h3>\n" +
" <div role=\"heading\" aria-level=\"5\">Bye</div>");
assertEquals(asList(
"<h1>Hello</h1>"
), page.locator("role=heading[level=1]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<h1>Hello</h1>"
), page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setLevel(1)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<h3>Hi</h3>"
), page.locator("role=heading[level=3]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<h3>Hi</h3>"
), page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setLevel(3)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"heading\" aria-level=\"5\">Bye</div>"
), page.locator("role=heading[level=5]").evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldFilterHiddenUnlessExplicitlyAskedFor() {
page.setContent("<button>Hi</button>\n" +
" <button hidden>Hello</button>\n" +
" <button aria-hidden=\"true\">Yay</button>\n" +
" <button aria-hidden=\"false\">Nay</button>\n" +
" <button style=\"visibility:hidden\">Bye</button>\n" +
" <div style=\"visibility:hidden\">\n" +
" <button>Oh</button>\n" +
" </div>\n" +
" <div style=\"visibility:hidden\">\n" +
" <button style=\"visibility:visible\">Still here</button>\n" +
" </div>\n" +
" <button style=\"display:none\">Never</button>\n" +
" <div id=host1></div>\n" +
" <div id=host2 style=\"display:none\"></div>\n" +
" <script>\n" +
" function addButton(host, text) {\n" +
" const root = host.attachShadow({ mode: 'open' });\n" +
" const button = document.createElement('button');\n" +
" button.textContent = text;\n" +
" root.appendChild(button);\n" +
" }\n" +
" addButton(document.getElementById('host1'), 'Shadow1');\n" +
" addButton(document.getElementById('host2'), 'Shadow2');\n" +
" </script>");
assertEquals(asList(
"<button>Hi</button>",
"<button aria-hidden=\"false\">Nay</button>",
"<button style=\"visibility:visible\">Still here</button>",
"<button>Shadow1</button>"
), page.locator("role=button").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button hidden=\"\">Hello</button>",
"<button aria-hidden=\"true\">Yay</button>",
"<button aria-hidden=\"false\">Nay</button>",
"<button style=\"visibility:hidden\">Bye</button>",
"<button>Oh</button>",
"<button style=\"visibility:visible\">Still here</button>",
"<button style=\"display:none\">Never</button>",
"<button>Shadow1</button>",
"<button>Shadow2</button>"
), page.locator("role=button[include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button hidden=\"\">Hello</button>",
"<button aria-hidden=\"true\">Yay</button>",
"<button aria-hidden=\"false\">Nay</button>",
"<button style=\"visibility:hidden\">Bye</button>",
"<button>Oh</button>",
"<button style=\"visibility:visible\">Still here</button>",
"<button style=\"display:none\">Never</button>",
"<button>Shadow1</button>",
"<button>Shadow2</button>"
), page.locator("role=button[include-hidden=true]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<button>Hi</button>",
"<button aria-hidden=\"false\">Nay</button>",
"<button style=\"visibility:visible\">Still here</button>",
"<button>Shadow1</button>"
), page.locator("role=button[include-hidden=false]").evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldSupportName() {
page.setContent("<div role=\"button\" aria-label=\" Hello \"></div>\n" +
" <div role=\"button\" aria-label=\"Hallo\"></div>\n" +
" <div role=\"button\" aria-label=\"Hello\" aria-hidden=\"true\"></div>\n" +
" <div role=\"button\" aria-label=\"123\" aria-hidden=\"true\"></div>\n" +
" <div role=\"button\" aria-label='foo\"bar' aria-hidden=\"true\"></div>");
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>"
), page.locator("role=button[name='Hello']").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(" \n Hello ")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hello")).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.locator("role=button[name*='all']").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.locator("role=button[name=/^H[ae]llo$/]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("^H[ae]llo$"))).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.locator("role=button[name=/h.*o/i]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("h.*o", Pattern.CASE_INSENSITIVE))).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hello\" aria-hidden=\"true\"></div>"
), page.locator("role=button[name='Hello'][include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hello\" aria-hidden=\"true\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hello").setIncludeHidden(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hello\" aria-hidden=\"true\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("hello").setIncludeHidden(true)).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>"
), page.locator("role=button[name=Hello]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\"123\" aria-hidden=\"true\"></div>"
), page.locator("role=button[name=123][include-hidden]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\"123\" aria-hidden=\"true\"></div>"
), 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());
}
}
@@ -0,0 +1,28 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestSelectorsText extends TestBase {
@Test
void hasTextAndInternalTextShouldMatchFullNodeTextInStrictMode() {
page.setContent("<div id=div1>hello<span>world</span></div>\n" +
" <div id=div2>hello</div>");
assertThat(page.getByText("helloworld", new Page.GetByTextOptions().setExact(true))).hasId("div1");
assertThat(page.getByText("hello", new Page.GetByTextOptions().setExact(true))).hasId("div2");
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("^helloworld$")))).hasId("div1");
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("^hello$")))).hasId("div2");
page.setContent("<div id=div1><span id=span1>hello</span>world</div>\n" +
" <div id=div2><span id=span2>hello</span></div>");
assertThat(page.getByText("helloworld", new Page.GetByTextOptions().setExact(true))).hasId("div1");
assertEquals(asList("span1", "span2"), page.getByText("hello", new Page.GetByTextOptions().setExact(true)).evaluateAll("els => els.map(e => e.id)"));
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("^helloworld$")))).hasId("div1");
assertThat(page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("^hello$")))).hasId("div2");
}
}
@@ -114,6 +114,9 @@ class Utils {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath.toFile()))) {
for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
Path toPath = toDir.resolve(zipEntry.getName());
if (!toPath.normalize().startsWith(toDir.normalize())) {
throw new IOException("Bad zip entry");
}
if (zipEntry.isDirectory()) {
Files.createDirectories(toPath);
} else {
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
+1 -1
View File
@@ -1 +1 @@
1.26.0
1.28.1
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -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)) {
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.26.0-SNAPSHOT</version>
<version>1.28.1</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
+11 -2
View File
@@ -5,8 +5,13 @@ ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-foc
# === INSTALL JDK and Maven ===
RUN apt-get update && \
# Install install jdk 17 in a separate apt-get command so that
# installing maven doesn't bring in jdk 11
apt-get install -y --no-install-recommends openjdk-17-jdk && \
apt-get install -y --no-install-recommends \
openjdk-17-jdk maven \
# Ubuntu 22.04 and earlier come with Maven 3.6.3 which fails with
# Java 17, so we install latest Maven from Apache instead.
# maven \
# Install utilities required for downloading browsers
curl \
# Install utilities required for downloading driver
@@ -17,8 +22,12 @@ RUN apt-get update && \
# Create the pwuser
adduser pwuser
RUN VERSION=3.8.6 && \
curl -o - https://dlcdn.apache.org/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz | tar zxfv - -C /opt/ && \
ln -s /opt/apache-maven-$VERSION/bin/mvn /usr/local/bin/
ARG PW_TARGET_ARCH
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-${PW_TARGET_ARCH}
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-${PW_TARGET_ARCH}
# === BAKE BROWSERS INTO IMAGE ===