1
0
mirror of synced 2026-05-23 03:03:15 +00:00

Compare commits

...

50 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
Yury Semikhatsky 6ef0cc29dd chore: roll driver to 1.26.0 (#1071) 2022-09-20 14:09:20 -07:00
pranesh517 8dfe959cce Added example in README.md for Gradle project (#1064) 2022-09-16 09:08:49 -07:00
Yury Semikhatsky 5979077403 fix: unflake TestPageRequestFallback.shouldChainOnce on win (#1058) 2022-09-12 16:44:14 -07:00
Yury Semikhatsky 020618d83d feat: roll beta driver (#1057) 2022-09-12 15:34:50 -07:00
Yury Semikhatsky 6bef6bb4a7 feat(docker): bump jdk version to 17 (#1052) 2022-09-08 15:03:48 -07:00
Yury Semikhatsky 734d56f5d5 test: make installation tests pass in docker (#1051) 2022-09-08 13:22:42 -07:00
Yury Semikhatsky 6e66861db3 feat: roll driver, support parameter for isEnabled/isEditable (#1049) 2022-09-08 13:05:33 -07:00
Yury Semikhatsky cf73a8d525 fix: serialize LocalDateTime parameters as Date (#1048) 2022-09-07 16:41:37 -07:00
Yury Semikhatsky 4a0ff6ef5c fix: log url for waitFor* methods (#1047) 2022-09-07 15:10:38 -07:00
Meir Blachman 398c979378 tests: remove duplicate cookies test (#1046) 2022-09-07 09:21:15 -07:00
Meir Blachman 39be690062 tests: unflake TestBrowserContextAddCookies.shouldRoundtripCookie (#1045) 2022-09-06 11:01:17 -07:00
Meir Blachman b5b0a983bd test: use assertThrows instead of try-catch - remaining tests (#1042) 2022-09-01 16:26:48 -07:00
Yury Semikhatsky 6b842e0d50 chore: roll driver, support new hasAttribute(name) (#1041) 2022-08-31 12:37:31 -07:00
Meir Blachman 321673407a tests: use assertThrows instead of try-catch (#1038) 2022-08-30 14:47:33 -07:00
Yury Semikhatsky 4d278c391e feat: allow using preinstalled node.js (#1030) 2022-08-15 20:51:07 -07:00
Yury Semikhatsky 5c47cfb1d5 chore: roll driver to 1.26.0-alpha-1660591492000 (#1031) 2022-08-15 13:07:43 -07:00
Yury Semikhatsky f036b3d38a chore: bump dev version to 1.26.0-SNAPSHOT (#1028) 2022-08-12 13:55:06 -07:00
Yury Semikhatsky 385131e51b chore: roll driver to 1.25.0 (#1026) 2022-08-11 13:40:57 -07:00
Yury Semikhatsky 6cb9f60988 chore: roll driver to 1.25.0-alpha-1659998098000 (#1025) 2022-08-09 14:07:53 -07:00
Yury Semikhatsky aef9badd64 feat: assertions default timeout (#1023) 2022-08-04 17:29:02 -07:00
Yury Semikhatsky 64f7a059af fix: prevent video.saveAs() from hanging (#1020) 2022-08-01 15:01:43 -07:00
Yury Semikhatsky 436fc12609 feat: roll driver to 1.25.0-alpha-jul-28-2022 (#1015) 2022-07-28 14:23:59 -07:00
Yury Semikhatsky 560575a9b5 test: unflake wheel test (#1014) 2022-07-27 16:39:47 -07:00
Yury Semikhatsky 0afaf6c561 fix: no split packages for compatibility with modules (#1013) 2022-07-27 13:32:25 -07:00
Yury Semikhatsky 202371b5d7 test: locator has with unicode symbols (#1012) 2022-07-26 14:12:47 -07:00
Yury Semikhatsky 2093bba554 fix: do not download browsers if selenium url is set (#1008) 2022-07-25 16:20:38 -07:00
Yury Semikhatsky 77538dcf7f chore: bump dev version to 1.25 (#1007) 2022-07-25 15:56:22 -07:00
150 changed files with 6226 additions and 2217 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 }}
+12 -4
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 -->104.0.5112.48<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->102.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,10 +43,18 @@ 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:
```gradle
dependencies {
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.27.0'
}
```
#### Is Playwright thread-safe?
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create multiple Playwright instances each on its own thread.
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.24.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -14,7 +14,9 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.net.URI;
@@ -26,7 +28,10 @@ import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
private final Path driverTempDir;
private Path preinstalledNodePath;
public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
@@ -37,11 +42,27 @@ public class DriverJar extends Driver {
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
driverTempDir.toFile().deleteOnExit();
String nodePath = System.getProperty("playwright.nodejs.path");
if (nodePath != null) {
preinstalledNodePath = Paths.get(nodePath);
if (!Files.exists(preinstalledNodePath)) {
throw new RuntimeException("Invalid Node.js path specified: " + nodePath);
}
}
logMessage("created DriverJar: " + driverTempDir);
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
protected void initialize(Boolean installBrowsers) throws Exception {
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
preinstalledNodePath = Paths.get(env.get(PLAYWRIGHT_NODEJS_PATH));
if (!Files.exists(preinstalledNodePath)) {
throw new RuntimeException("Invalid Node.js path specified: " + preinstalledNodePath);
}
} else if (preinstalledNodePath != null) {
// Pass the env variable to the driver process.
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
}
extractDriverToTempDir();
logMessage("extracted driver from jar to " + driverPath());
if (installBrowsers)
@@ -57,13 +78,16 @@ public class DriverJar extends Driver {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
return;
}
Path driver = driverPath();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
}
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.environment().putAll(env);
setRequiredEnvironmentVariables(pb);
ProcessBuilder pb = createProcessBuilder();
pb.command().add("install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
@@ -82,9 +106,10 @@ public class DriverJar extends Driver {
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
}
private void extractDriverToTempDir() throws URISyntaxException, IOException {
void extractDriverToTempDir() throws URISyntaxException, IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
URI originalUri = classloader.getResource(
"driver/" + platformDir()).toURI();
URI uri = maybeExtractNestedJar(originalUri);
// Create zip filesystem if loading from jar.
@@ -96,6 +121,12 @@ public class DriverJar extends Driver {
// See https://github.com/microsoft/playwright-java/issues/306
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
Files.walk(srcRoot).forEach(fromPath -> {
if (preinstalledNodePath != null) {
String fileName = fromPath.getFileName().toString();
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
return;
}
}
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
Path toPath = driverTempDir.resolve(relative.toString());
try {
@@ -158,7 +189,7 @@ public class DriverJar extends Driver {
}
@Override
Path driverDir() {
protected Path driverDir() {
return driverTempDir;
}
}
@@ -14,17 +14,17 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.DriverJar;
import com.microsoft.playwright.impl.driver.Driver;
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;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
@@ -32,6 +32,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.microsoft.playwright.impl.driver.jar.DriverJar.PLAYWRIGHT_NODEJS_PATH;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.*;
public class TestInstall {
@@ -57,6 +59,7 @@ public class TestInstall {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
System.clearProperty("playwright.driver.tmpdir");
System.clearProperty("playwright.nodejs.path");
// Clear system property to ensure that the default driver is loaded.
System.clearProperty("playwright.driver.impl");
}
@@ -72,27 +75,18 @@ public class TestInstall {
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
for (int i = 0; i < 2; i++){
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
String message = exception.getMessage();
assertTrue(message.contains("Failed to create driver"), message);
}
field.set(Driver.class, value);
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.createAndInstall(env, true));
String message = exception.getMessage();
assertTrue(message.contains("Failed to create driver"), message);
}
@Test
void playwrightCliInstalled() throws Exception {
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
assertTrue(Files.exists(cli));
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
assertTrue(Files.exists(driver.driverPath()));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
@@ -109,24 +103,62 @@ public class TestInstall {
@Test
void playwrightDriverDefaultImpl() {
assertDoesNotThrow(() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
assertDoesNotThrow(() -> Driver.createAndInstall(Collections.emptyMap(), false));
}
@Test
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
() -> Driver.createAndInstall(Collections.emptyMap(), false));
assertEquals("Failed to create driver", thrown.getMessage());
}
field.set(Driver.class, value);
@Test
void canPassPreinstalledNodeJsAsSystemProperty(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
String nodePath = extractNodeJsToTemp();
System.setProperty("playwright.nodejs.path", nodePath);
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
}
@Test
void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
String nodePath = extractNodeJsToTemp();
Driver driver = Driver.createAndInstall(singletonMap(PLAYWRIGHT_NODEJS_PATH, nodePath), false);
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
}
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
DriverJar auxDriver = new DriverJar();
auxDriver.extractDriverToTempDir();
String nodePath = auxDriver.driverPath().getParent().resolve(isWindows() ? "node.exe" : "node").toString();
return nodePath;
}
private static boolean isWindows() {
String name = System.getProperty("os.name").toLowerCase();
return name.contains("win");
}
private static void canSpecifyPreinstalledNodeJsShared(Driver driver, Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
Path builtinNode = driver.driverPath().getParent().resolve("node");
assertFalse(Files.exists(builtinNode), builtinNode.toString());
Path builtinNodeExe = driver.driverPath().getParent().resolve("node.exe");
assertFalse(Files.exists(builtinNodeExe), builtinNodeExe.toString());
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("--version");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Path out = tmpDir.resolve("out.txt");
pb.redirectOutput(out.toFile());
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for version to be printed");
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
assertTrue(stdout.contains("Version "), stdout);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.24.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>driver</artifactId>
@@ -14,14 +14,15 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
/**
* This class provides access to playwright-cli. It can be either preinstalled
@@ -29,6 +30,8 @@ import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
* loaded from the driver-bundle module if that module is in the classpath.
*/
public abstract class Driver {
protected final Map<String, String> env = new LinkedHashMap<>();
private static Driver instance;
private static class PreinstalledDriver extends Driver {
@@ -39,32 +42,28 @@ public abstract class Driver {
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
protected void initialize(Boolean installBrowsers) {
// no-op
}
@Override
Path driverDir() {
protected Path driverDir() {
return driverDir;
}
}
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
if (instance == null) {
try {
instance = createDriver();
logMessage("initializing driver");
instance.initialize(env, installBrowsers);
logMessage("driver initialized.");
} catch (Exception exception) {
instance = null;
throw new RuntimeException("Failed to create driver", exception);
}
instance = createAndInstall(env, installBrowsers);
}
return instance.driverPath();
return instance;
}
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
private void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
this.env.putAll(env);
initialize(installBrowsers);
}
protected abstract void initialize(Boolean installBrowsers) throws Exception;
public Path driverPath() {
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
@@ -72,13 +71,16 @@ public abstract class Driver {
return driverDir().resolve(cliFileName);
}
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
public ProcessBuilder createProcessBuilder() {
ProcessBuilder pb = new ProcessBuilder(driverPath().toString());
pb.environment().putAll(env);
pb.environment().put("PW_LANG_NAME", "java");
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
String version = Driver.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
return pb;
}
private static String getMajorJavaVersion() {
@@ -92,20 +94,31 @@ public abstract class Driver {
}
return version;
}
public static Driver createAndInstall(Map<String, String> env, Boolean installBrowsers) {
try {
Driver instance = newInstance();
logMessage("initializing driver");
instance.initialize(env, installBrowsers);
logMessage("driver initialized.");
return instance;
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
}
private static Driver createDriver() throws Exception {
private static Driver newInstance() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
return new PreinstalledDriver(Paths.get(pathFromProperty));
}
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
abstract Path driverDir();
protected abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright.impl.driver;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.24.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.24.0-SNAPSHOT</version>
<version>1.28.1</version>
</parent>
<artifactId>playwright</artifactId>
@@ -30,15 +30,15 @@ import java.nio.file.Path;
*
* <p> **Cookie management**
*
* <p> {@code APIRequestContext} retuned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
* log in using this API, your e2e test will be logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you shoud create a new {@code APIRequestContext} by calling
* {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own isolated cookie
* storage.
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own
* isolated cookie storage.
*/
public interface APIRequestContext {
class StorageStateOptions {
@@ -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,12 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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.
@@ -146,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;
@@ -179,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.
@@ -261,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;
}
/**
@@ -283,13 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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) {
@@ -387,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) {
@@ -453,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;
}
/**
@@ -567,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}.
*/
@@ -580,12 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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.
@@ -632,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;
@@ -665,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.
@@ -747,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;
}
/**
@@ -769,13 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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) {
@@ -873,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) {
@@ -939,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;
}
/**
@@ -1099,7 +1095,7 @@ public interface Browser extends AutoCloseable {
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
*
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicilty close the returned context
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
@@ -1111,7 +1107,7 @@ public interface Browser extends AutoCloseable {
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Gracefull close up everything
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
@@ -1122,7 +1118,7 @@ public interface Browser extends AutoCloseable {
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
*
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicilty close the returned context
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
* artifacts—like HARs and videos—are fully flushed and saved.
@@ -1134,7 +1130,7 @@ public interface Browser extends AutoCloseable {
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Gracefull close up everything
* // Graceful close up everything
* context.close();
* browser.close();
* }</pre>
@@ -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,12 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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}.
@@ -518,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;
@@ -551,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.
@@ -669,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;
}
/**
@@ -724,13 +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"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
* 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) {
@@ -867,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) {
@@ -933,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;
}
/**
@@ -1028,7 +1026,9 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -1036,7 +1036,9 @@ public interface BrowserType {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -1047,6 +1049,11 @@ public interface BrowserType {
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
* BrowserContext defaultContext = browser.contexts().get(0);
* Page page = defaultContext.pages().get(0);
* }</pre>
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
@@ -1060,6 +1067,11 @@ public interface BrowserType {
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
* BrowserContext defaultContext = browser.contexts().get(0);
* Page page = defaultContext.pages().get(0);
* }</pre>
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
@@ -29,10 +29,9 @@ import static java.util.Arrays.asList;
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = new ProcessBuilder(driver.toString());
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().addAll(asList(args));
Driver.setRequiredEnvironmentVariables(pb);
String version = Playwright.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
@@ -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.
@@ -586,7 +601,7 @@ public interface ElementHandle extends JSHandle {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -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"}.
@@ -647,7 +662,7 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -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"}.
@@ -1166,7 +1181,7 @@ public interface ElementHandle extends JSHandle {
*/
public WaitForSelectorState state;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
*/
public Boolean strict;
@@ -1193,7 +1208,7 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
*/
public WaitForSelectorOptions setStrict(boolean strict) {
@@ -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>
*/
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.regex.Pattern;
/**
@@ -23,20 +24,20 @@ 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>
*
* <p> **Strictness**
*
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
* given selector.
* 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.
@@ -1013,7 +1326,7 @@ public interface Locator {
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public List<Locator> mask;
@@ -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"}.
@@ -1074,7 +1387,7 @@ public interface Locator {
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
@@ -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>
*
File diff suppressed because it is too large Load Diff
@@ -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 {
@@ -40,7 +40,7 @@ public interface Response {
*/
Frame frame();
/**
* Indicates whether this Response was fullfilled by a Service Worker's Fetch Handler (i.e. via <a
* Indicates whether this Response was fulfilled by a Service Worker's Fetch Handler (i.e. via <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith">FetchEvent.respondWith</a>).
*/
boolean fromServiceWorker();
@@ -44,7 +44,7 @@ public interface Selectors {
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
@@ -71,7 +71,7 @@ public interface Selectors {
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
*/
default void register(String name, String script) {
register(name, script, null);
@@ -79,7 +79,7 @@ public interface Selectors {
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
@@ -106,13 +106,13 @@ public interface Selectors {
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
*/
void register(String name, String script, RegisterOptions options);
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
@@ -139,7 +139,7 @@ public interface Selectors {
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
*/
default void register(String name, Path script) {
register(name, script, null);
@@ -147,7 +147,7 @@ public interface Selectors {
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
@@ -174,8 +174,15 @@ public interface Selectors {
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
* @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);
@@ -46,7 +46,7 @@ public interface APIResponseAssertions {
*/
APIResponseAssertions not();
/**
* Ensures the response status code is within [200..299] range.
* Ensures the response status code is within {@code 200..299} range.
* <pre>{@code
* assertThat(response).isOK();
* }</pre>
@@ -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");
* }
* }
@@ -72,11 +72,16 @@ public interface LocatorAssertions {
}
}
class IsEditableOptions {
public Boolean editable;
/**
* Time to retry the assertion for.
*/
public Double timeout;
public IsEditableOptions setEditable(boolean editable) {
this.editable = editable;
return this;
}
/**
* Time to retry the assertion for.
*/
@@ -100,11 +105,16 @@ public interface LocatorAssertions {
}
}
class IsEnabledOptions {
public Boolean enabled;
/**
* Time to retry the assertion for.
*/
public Double timeout;
public IsEnabledOptions setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
/**
* Time to retry the assertion for.
*/
@@ -146,6 +156,7 @@ public interface LocatorAssertions {
* Time to retry the assertion for.
*/
public Double timeout;
public Boolean visible;
/**
* Time to retry the assertion for.
@@ -154,6 +165,10 @@ public interface LocatorAssertions {
this.timeout = timeout;
return this;
}
public IsVisibleOptions setVisible(boolean visible) {
this.visible = visible;
return this;
}
}
class ContainsTextOptions {
/**
@@ -354,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() {
@@ -363,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);
@@ -394,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() {
@@ -403,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);
@@ -442,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() {
@@ -451,13 +466,13 @@ 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);
/**
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
* Ensures that {@code Locator} either does not resolve to any DOM node, or resolves to a <a
* href="https://playwright.dev/java/docs/api/actionability#visible">non-visible</a> one.
* <pre>{@code
* assertThat(page.locator(".my-element")).isHidden();
* }</pre>
@@ -466,16 +481,16 @@ public interface LocatorAssertions {
isHidden(null);
}
/**
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
* Ensures that {@code Locator} either does not resolve to any DOM node, or resolves to a <a
* href="https://playwright.dev/java/docs/api/actionability#visible">non-visible</a> one.
* <pre>{@code
* assertThat(page.locator(".my-element")).isHidden();
* }</pre>
*/
void isHidden(IsHiddenOptions options);
/**
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
* node.
* 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();
* }</pre>
@@ -484,8 +499,8 @@ public interface LocatorAssertions {
isVisible(null);
}
/**
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
* node.
* 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();
* }</pre>
@@ -498,9 +513,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -515,9 +550,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -530,9 +585,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -547,9 +622,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -562,9 +657,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -579,9 +694,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -594,9 +729,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -611,9 +766,29 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).containsText("substring");
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> Elements from a **subset** of this list contain text from the expected array, respectively.</li>
* <li> The matching subset of elements has the same order as the expected array.</li>
* <li> Each text value from the expected array is matched by some element from the list.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
*
* // No item contains this text
* assertThat(page.locator("ul > li")).containsText(new String[] {"Some 33"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -629,7 +804,7 @@ public interface LocatorAssertions {
* @param value Expected attribute value.
*/
default void hasAttribute(String name, String value) {
hasAttribute(name, value, null);
hasAttribute(name, value, (HasAttributeOptions) null);
}
/**
* Ensures the {@code Locator} points to an element with given attribute.
@@ -651,7 +826,7 @@ public interface LocatorAssertions {
* @param value Expected attribute value.
*/
default void hasAttribute(String name, Pattern value) {
hasAttribute(name, value, null);
hasAttribute(name, value, (HasAttributeOptions) null);
}
/**
* Ensures the {@code Locator} points to an element with given attribute.
@@ -822,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.
@@ -834,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.
@@ -844,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.
@@ -856,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.
@@ -866,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.
@@ -877,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.
@@ -886,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.
@@ -897,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.
@@ -934,9 +1109,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -951,9 +1145,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -966,9 +1179,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -983,9 +1215,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -998,9 +1249,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1015,9 +1285,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1030,9 +1319,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -1047,9 +1355,28 @@ public interface LocatorAssertions {
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> If you pass an array as an expected value, the expectations are:
* <ol>
* <li> Locator resolves to a list of elements.</li>
* <li> The number of elements equals the number of expected values in the array.</li>
* <li> Elements from the list have text matching expected array values, one by one, in order.</li>
* </ol>
*
* <p> For example, consider the following list:
*
* <p> Let's see how we can use the assertion:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* // Has the right items in the right order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 3", "Text 2", "Text 1"});
*
* // Last item does not match
* assertThat(page.locator("ul > li")).hasText(new String[] {"Text 1", "Text 2", "Text"});
*
* // Locator points to the outer list element, not to the list items
* assertThat(page.locator("ul")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
* }</pre>
*
* @param expected Expected substring or RegExp or a list of those.
@@ -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"));
* }
* }
@@ -20,6 +20,7 @@ import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
import com.microsoft.playwright.impl.AssertionsTimeout;
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
import com.microsoft.playwright.impl.PageAssertionsImpl;
@@ -86,5 +87,17 @@ public interface PlaywrightAssertions {
return new PageAssertionsImpl(page);
}
/**
* Changes default timeout for Playwright assertions from 5 seconds to the specified value.
* <pre>{@code
* PlaywrightAssertions.setDefaultAssertionTimeout(30_000);
* }</pre>
*
* @param timeout Timeout in milliseconds.
*/
static void setDefaultAssertionTimeout(double milliseconds) {
AssertionsTimeout.setDefaultTimeout(milliseconds);
}
}
@@ -110,6 +110,12 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options.ignoreHTTPSErrors != null) {
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
}
if (options.maxRedirects != null) {
if (options.maxRedirects < 0) {
throw new PlaywrightException("'maxRedirects' should be greater than or equal to '0'");
}
params.addProperty("maxRedirects", options.maxRedirects);
}
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
@@ -21,6 +21,7 @@ import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
import java.util.regex.Pattern;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
@@ -54,6 +55,20 @@ public class APIResponseAssertionsImpl implements APIResponseAssertions {
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
throw new AssertionFailedError(message + log);
String contentType = actual.headers().get("content-type");
boolean isTextEncoding = contentType == null ? false : isTextualMimeType(contentType);
String responseText = "";
if (isTextEncoding) {
String text = actual.text();
if (text != null) {
responseText = "\nResponse text:\n" + (text.length() > 1000 ? text.substring(0, 1000) : text);
}
}
throw new AssertionFailedError(message + log + responseText);
}
static boolean isTextualMimeType(String mimeType) {
return Pattern.matches("^(text/.*?|application/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image/svg(\\+xml)?|application/.*?(\\+json|\\+xml))(;\\s*charset=.*)?$", mimeType);
}
}
@@ -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);
@@ -52,7 +52,7 @@ class AssertionsBase {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = 5_000.0;
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
@@ -0,0 +1,30 @@
/*
* 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.impl;
import com.microsoft.playwright.PlaywrightException;
public class AssertionsTimeout {
static double defaultTimeout = 5_000;
public static void setDefaultTimeout(double ms) {
if (ms < 0) {
throw new PlaywrightException("Timeout cannot be negative");
}
defaultTimeout = ms;
}
}
@@ -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,8 +90,7 @@ 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("APIRequestContext").get("guid").getAsString());
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
}
void setRecordHar(Path path, HarContentPolicy policy) {
@@ -170,7 +177,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public Page waitForPage(WaitForPageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.close", () -> waitForPageImpl(options, code));
return withWaitLogging("BrowserContext.close", logger -> waitForPageImpl(options, code));
}
private Page waitForPageImpl(WaitForPageOptions options, Runnable code) {
@@ -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();
@@ -22,11 +22,13 @@ import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
class ChannelOwner extends LoggingSupport {
final Connection connection;
private final ChannelOwner parent;
private ChannelOwner parent;
private final Map<String, ChannelOwner> objects = new HashMap<>();
final String type;
@@ -68,7 +70,13 @@ class ChannelOwner extends LoggingSupport {
objects.clear();
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
void adopt(ChannelOwner child) {
child.parent.objects.remove(child.guid);
objects.put(child.guid, child);
child.parent = this;
}
<T> T withWaitLogging(String apiName, Function<Logger, T> code) {
return new WaitForEventLogger<>(this, apiName, code).get();
}
@@ -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);
}
@@ -201,18 +212,24 @@ public class Connection {
createRemoteObject(message.guid, message.params);
return;
}
if (message.method.equals("__dispose__")) {
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to dispose: " + message.guid);
}
object.disconnect();
return;
}
ChannelOwner object = objects.get(message.guid);
if (object == null) {
throw new PlaywrightException("Cannot find object to call " + message.method + ": " + message.guid);
}
if (message.method.equals("__adopt__")) {
String childGuid = message.params.get("guid").getAsString();
ChannelOwner child = objects.get(childGuid);
if (child == null) {
throw new PlaywrightException("Unknown new child: " + childGuid);
}
object.adopt(child);
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
return;
}
object.handleEvent(message.method, message.params);
}
@@ -277,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();
@@ -811,17 +872,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Frame.waitForLoadState", () -> {
waitForLoadStateImpl(state, options);
withWaitLogging("Frame.waitForLoadState", logger -> {
waitForLoadStateImpl(state, options, logger);
return null;
});
}
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options);
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options, Logger logger) {
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options, logger);
}
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options, Logger logger) {
if (options == null) {
options = new WaitForLoadStateOptions();
}
@@ -830,7 +891,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
List<Waitable<Void>> waitables = new ArrayList<>();
waitables.add(new WaitForLoadStateHelper(state));
waitables.add(new WaitForLoadStateHelper(state, logger));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(options.timeout));
runUntil(() -> {}, new WaitableRace<>(waitables));
@@ -838,10 +899,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
private final WaitUntilState expectedState;
private final Logger logger;
private boolean isDone;
WaitForLoadStateHelper(WaitUntilState state) {
WaitForLoadStateHelper(WaitUntilState state, Logger logger) {
expectedState = state;
this.logger = logger;
isDone = loadStates.contains(state);
if (!isDone) {
internalListeners.add(InternalEventType.LOADSTATE, this);
@@ -850,6 +913,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void accept(WaitUntilState state) {
logger.log(" load state changed to " + state);
if (expectedState.equals(state)) {
isDone = true;
dispose();
@@ -873,20 +937,24 @@ public class FrameImpl extends ChannelOwner implements Frame {
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
private final UrlMatcher matcher;
private final WaitUntilState expectedLoadState;
private final Logger logger;
private WaitForLoadStateHelper loadStateHelper;
private RequestImpl request;
private RuntimeException exception;
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState, Logger logger) {
this.matcher = matcher;
this.expectedLoadState = expectedLoadState;
this.logger = logger;
internalListeners.add(InternalEventType.NAVIGATED, this);
}
@Override
public void accept(JsonObject params) {
if (!matcher.test(params.get("url").getAsString())) {
String url = params.get("url").getAsString();
logger.log(" navigated to " + url);
if (!matcher.test(url)) {
return;
}
if (params.has("error")) {
@@ -898,7 +966,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
request = connection.getExistingObject(jsonReq.get("guid").getAsString());
}
}
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState);
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState, logger);
}
internalListeners.remove(InternalEventType.NAVIGATED, this);
}
@@ -937,14 +1005,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
return withWaitLogging("Frame.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options, null));
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
return waitForNavigationImpl(code, options, null);
Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
return waitForNavigationImpl(logger, code, options, null);
}
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
if (options == null) {
options = new WaitForNavigationOptions();
}
@@ -956,7 +1024,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
logger.log("waiting for navigation " + matcher);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
@@ -1014,18 +1083,22 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
withWaitLogging("Frame.waitForURL", logger -> {
waitForURLImpl(logger, matcher, options);
return null;
});
}
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
void waitForURLImpl(Logger logger, UrlMatcher matcher, WaitForURLOptions options) {
logger.log("waiting for url " + matcher);
if (options == null) {
options = new WaitForURLOptions();
}
if (matcher.test(url())) {
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class), logger);
return;
}
waitForNavigationImpl(() -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
waitForNavigationImpl(logger, () -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
}
int queryCount(String selector) {
@@ -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
@@ -26,8 +26,7 @@ import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
import static com.microsoft.playwright.impl.LoggingSupport.isApiLoggingEnabled;
import static com.microsoft.playwright.impl.LoggingSupport.logApi;
import static com.microsoft.playwright.impl.LoggingSupport.*;
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -67,9 +66,7 @@ public class HARRouter {
String action = response.get("action").getAsString();
if ("redirect".equals(action)) {
String redirectURL = response.get("redirectURL").getAsString();
if (isApiLoggingEnabled()) {
logApi("HAR: " + route.request().url() + " redirected to " + redirectURL);
}
logApiIfEnabled("HAR: " + route.request().url() + " redirected to " + redirectURL);
((RouteImpl) route).redirectNavigationRequest(redirectURL);
return;
}
@@ -86,9 +83,7 @@ public class HARRouter {
}
if ("error".equals(action)) {
if (isApiLoggingEnabled()) {
logApi("HAR: " + response.get("message").getAsString());
}
logApiIfEnabled("HAR: " + response.get("message").getAsString());
// Report the error, but fall through to the default handler.
}
@@ -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);
}
}
@@ -299,7 +299,9 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void isEditable(IsEditableOptions options) {
expectTrue("to.be.editable", "Locator expected to be editable", convertType(options, FrameExpectOptions.class));
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean editable = options == null || options.editable == null || options.editable == true;
expectTrue(editable ? "to.be.editable" : "to.be.readonly", "Locator expected to be editable", frameOptions);
}
@Override
@@ -309,7 +311,9 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void isEnabled(IsEnabledOptions options) {
expectTrue("to.be.enabled", "Locator expected to be enabled", convertType(options, FrameExpectOptions.class));
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean enabled = options == null || options.enabled == null || options.enabled == true;
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", "Locator expected to be enabled", frameOptions);
}
@Override
@@ -324,7 +328,9 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void isVisible(IsVisibleOptions options) {
expectTrue("to.be.visible", "Locator expected to be visible", convertType(options, FrameExpectOptions.class));
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean visible = options == null || options.visible == null || options.visible == true;
expectTrue(visible ? "to.be.visible" : "to.be.hidden", "Locator expected to be visible", frameOptions);
}
private void expectTrue(String expression, String message, FrameExpectOptions options) {
@@ -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);
}
}
@@ -0,0 +1,21 @@
/*
* 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.impl;
interface Logger {
void log(String message);
}
@@ -60,8 +60,10 @@ class LoggingSupport {
System.err.println(timestamp + " " + message);
}
static boolean isApiLoggingEnabled() {
return isEnabled;
static void logApiIfEnabled(String message) {
if (isEnabled) {
logApi(message);
}
}
static void logApi(String message) {
@@ -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);
@@ -443,7 +417,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Page.waitForClose", () -> waitForCloseImpl(options, code));
return withWaitLogging("Page.waitForClose", logger -> waitForCloseImpl(options, code));
}
private Page waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
@@ -455,7 +429,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
return withWaitLogging("Page.waitForConsoleMessage", () -> waitForConsoleMessageImpl(options, code));
return withWaitLogging("Page.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
}
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
@@ -467,7 +441,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Download waitForDownload(WaitForDownloadOptions options, Runnable code) {
return withWaitLogging("Page.waitForDownload", () -> waitForDownloadImpl(options, code));
return withWaitLogging("Page.waitForDownload", logger -> waitForDownloadImpl(options, code));
}
private Download waitForDownloadImpl(WaitForDownloadOptions options, Runnable code) {
@@ -479,7 +453,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public FileChooser waitForFileChooser(WaitForFileChooserOptions options, Runnable code) {
return withWaitLogging("Page.waitForFileChooser", () -> waitForFileChooserImpl(options, code));
return withWaitLogging("Page.waitForFileChooser", logger -> waitForFileChooserImpl(options, code));
}
private FileChooser waitForFileChooserImpl(WaitForFileChooserOptions options, Runnable code) {
@@ -492,7 +466,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Page waitForPopup(WaitForPopupOptions options, Runnable code) {
return withWaitLogging("Page.waitForPopup", () -> waitForPopupImpl(options, code));
return withWaitLogging("Page.waitForPopup", logger -> waitForPopupImpl(options, code));
}
private Page waitForPopupImpl(WaitForPopupOptions options, Runnable code) {
@@ -504,7 +478,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public WebSocket waitForWebSocket(WaitForWebSocketOptions options, Runnable code) {
return withWaitLogging("Page.waitForWebSocket", () -> waitForWebSocketImpl(options, code));
return withWaitLogging("Page.waitForWebSocket", logger -> waitForWebSocketImpl(options, code));
}
private WebSocket waitForWebSocketImpl(WaitForWebSocketOptions options, Runnable code) {
@@ -516,7 +490,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Worker waitForWorker(WaitForWorkerOptions options, Runnable code) {
return withWaitLogging("Page.waitForWorker", () -> waitForWorkerImpl(options, code));
return withWaitLogging("Page.waitForWorker", logger -> waitForWorkerImpl(options, code));
}
private Worker waitForWorkerImpl(WaitForWorkerOptions options, Runnable code) {
@@ -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));
@@ -1268,25 +1313,25 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Page.waitForLoadState", () -> {
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class));
withWaitLogging("Page.waitForLoadState", logger -> {
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
return null;
});
}
@Override
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Page.waitForNavigation", () -> waitForNavigationImpl(code, options));
return withWaitLogging("Page.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options));
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
Frame.WaitForNavigationOptions frameOptions = new Frame.WaitForNavigationOptions();
if (options != null) {
frameOptions.timeout = options.timeout;
frameOptions.waitUntil = options.waitUntil;
frameOptions.url = options.url;
}
return mainFrame.waitForNavigationImpl(code, frameOptions);
return mainFrame.waitForNavigationImpl(logger, code, frameOptions);
}
void frameNavigated(FrameImpl frame) {
@@ -1338,21 +1383,28 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(toRequestPredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
return waitForRequest(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
public Request waitForRequest(Pattern urlPattern, WaitForRequestOptions options, Runnable code) {
return waitForRequest(toRequestPredicate(new UrlMatcher(urlPattern)), options, code);
return waitForRequest(new UrlMatcher(urlPattern), null, options, code);
}
@Override
public Request waitForRequest(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
return withWaitLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
return waitForRequest(null, predicate, options, code);
}
private static Predicate<Request> toRequestPredicate(UrlMatcher matcher) {
return request -> matcher.test(request.url());
private Request waitForRequest(UrlMatcher urlMatcher, Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
return withWaitLogging("Page.waitForRequest", logger -> {
logger.log("waiting for request " + ((urlMatcher == null) ? "matching predicate" : urlMatcher.toString()));
Predicate<Request> requestPredicate = predicate;
if (requestPredicate == null) {
requestPredicate = request -> urlMatcher.test(request.url());;
}
return waitForRequestImpl(requestPredicate, options, code);
});
}
private Request waitForRequestImpl(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
@@ -1364,7 +1416,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequestFinished(WaitForRequestFinishedOptions options, Runnable code) {
return withWaitLogging("Page.waitForRequestFinished", () -> waitForRequestFinishedImpl(options, code));
return withWaitLogging("Page.waitForRequestFinished", logger -> waitForRequestFinishedImpl(options, code));
}
private Request waitForRequestFinishedImpl(WaitForRequestFinishedOptions options, Runnable code) {
@@ -1376,21 +1428,28 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
return waitForResponse(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
public Response waitForResponse(Pattern urlPattern, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(urlPattern)), options, code);
return waitForResponse(new UrlMatcher(urlPattern), null, options, code);
}
@Override
public Response waitForResponse(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
return withLogging("Page.waitForResponse", () -> waitForResponseImpl(predicate, options, code));
return waitForResponse(null, predicate, options, code);
}
private static Predicate<Response> toResponsePredicate(UrlMatcher matcher) {
return response -> matcher.test(response.url());
private Response waitForResponse(UrlMatcher urlMatcher, Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
return withWaitLogging("Page.waitForResponse", logger -> {
logger.log("waiting for response " + ((urlMatcher == null) ? "matching predicate" : urlMatcher.toString()));
Predicate<Response> responsePredicate = predicate;
if (responsePredicate == null) {
responsePredicate = response -> urlMatcher.test(response.url());;
}
return waitForResponseImpl(responsePredicate, options, code);
});
}
private Response waitForResponseImpl(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
@@ -1427,7 +1486,10 @@ public class PageImpl extends ChannelOwner implements Page {
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertType(options, Frame.WaitForURLOptions.class)));
withWaitLogging("Page.waitForURL", logger -> {
mainFrame.waitForURLImpl(logger, matcher, convertType(options, Frame.WaitForURLOptions.class));
return null;
});
}
@Override
@@ -21,9 +21,9 @@ import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -32,16 +32,21 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private Process driverProcess;
public static PlaywrightImpl create(CreateOptions options) {
return createImpl(options, false);
}
public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewDriverInstanceForTests) {
Map<String, String> env = Collections.emptyMap();
if (options != null && options.env != null) {
env = options.env;
}
Driver driver = forceNewDriverInstanceForTests ?
Driver.createAndInstall(env, true) :
Driver.ensureDriverInstalled(env, true);
try {
Map<String, String> env = Collections.emptyMap();
if (options != null && options.env != null) {
env = options.env;
}
Path driver = Driver.ensureDriverInstalled(env, true);
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("run-driver");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.environment().putAll(env);
Driver.setRequiredEnvironmentVariables(pb);
Process p = pb.start();
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
PlaywrightImpl result = connection.initializePlaywright();
@@ -32,6 +32,7 @@ public class RequestOptionsImpl implements RequestOptions {
Boolean failOnStatusCode;
Boolean ignoreHTTPSErrors;
Double timeout;
Integer maxRedirects;
@Override
public RequestOptions setHeader(String name, String value) {
@@ -118,4 +119,10 @@ public class RequestOptionsImpl implements RequestOptions {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
@Override
public RequestOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
}
@@ -31,10 +31,12 @@ import java.util.Map;
import static com.microsoft.playwright.impl.Utils.convertType;
public class RouteImpl extends ChannelOwner implements Route {
private final RequestImpl request;
private boolean handled;
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
@Override
@@ -196,7 +198,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public RequestImpl request() {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
return request;
}
void redirectNavigationRequest(String redirectURL) {
@@ -38,19 +38,16 @@ class Router {
this.times = times;
}
boolean handle(RouteImpl route) {
if (!matcher.test(route.request().url())) {
return false;
}
if (times != null) {
--times;
}
void handle(RouteImpl route) {
handler.accept(route);
return true;
}
boolean isDone() {
return times != null && times <= 0;
boolean decrementRemainingCallCount() {
if (times == null) {
return false;
}
--times;
return times <= 0;
}
}
@@ -73,14 +70,16 @@ class Router {
HandleResult result = HandleResult.NoMatchingHandler;
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
RouteInfo info = it.next();
if (info.handle(route)) {
result = HandleResult.FoundMatchingHandler;
if (info.isDone()) {
it.remove();
}
if (route.isHandled()) {
break;
}
if (!info.matcher.test(route.request().url())) {
continue;
}
if (info.decrementRemainingCallCount()) {
it.remove();
}
result = HandleResult.FoundMatchingHandler;
info.handle(route);
if (route.isHandled()) {
break;
}
}
return result;
@@ -33,6 +33,8 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Pattern;
@@ -150,6 +152,8 @@ class Serialization {
result.s = (String) value;
} else if (value instanceof Date) {
result.d = ((Date)value).toInstant().toString();
} else if (value instanceof LocalDateTime) {
result.d = ((LocalDateTime)value).atZone(ZoneId.systemDefault()).toInstant().toString();
} else if (value instanceof URL) {
result.u = ((URL)value).toString();
} else if (value instanceof Pattern) {
@@ -374,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);
}
@@ -97,4 +97,13 @@ class UrlMatcher {
public int hashCode() {
return Objects.hash(rawSource);
}
@Override
public String toString() {
if (rawSource == null)
return "<any>";
if (rawSource instanceof Predicate)
return "matching predicate";
return rawSource.toString();
}
}
@@ -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 {
@@ -72,6 +69,9 @@ class VideoImpl implements Video {
@Override
public void saveAs(Path path) {
page.withLogging("Video.saveAs", () -> {
if (!page.isClosed()) {
throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs");
}
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
@@ -18,38 +18,52 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.microsoft.playwright.impl.Utils.createGuid;
public class WaitForEventLogger<T> implements Supplier<T> {
private final Supplier<T> supplier;
public class WaitForEventLogger<T> implements Supplier<T>, Logger {
private final Function<Logger, T> supplier;
private final ChannelOwner channel;
private final String waitId;
private final String apiName;
WaitForEventLogger(ChannelOwner channelOwner, String apiName, Supplier<T> supplier) {
WaitForEventLogger(ChannelOwner channelOwner, String apiName, Function<Logger, T> supplier) {
this.supplier = supplier;
this.channel = channelOwner;
this.apiName = apiName;
this.waitId = createGuid();
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
}
@Override
public T get() {
return channel.withLogging(apiName, () -> {
{
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
}
JsonObject info = new JsonObject();
info.addProperty("phase", "after");
try {
return supplier.apply(this);
} catch (RuntimeException e) {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
}
});
}
@Override
public void log(String message) {
LoggingSupport.logApiIfEnabled(message);
JsonObject info = new JsonObject();
info.addProperty("phase", "after");
try {
return supplier.get();
} catch (RuntimeException e) {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
}
info.addProperty("phase", "log");
info.addProperty("message", message);
sendWaitForEventInfo(info);
}
private void sendWaitForEventInfo(JsonObject info) {
@@ -57,6 +71,6 @@ public class WaitForEventLogger<T> implements Supplier<T> {
info.addProperty("waitId", waitId);
JsonObject params = new JsonObject();
params.add("info", info);
channel.withLogging(apiName, () -> channel.sendMessageAsync("waitForEventInfo", params));
channel.sendMessageAsync("waitForEventInfo", params);
}
}
@@ -87,7 +87,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
@Override
public WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable code) {
return withWaitLogging("WebSocket.waitForFrameReceived", () -> waitForFrameReceivedImpl(options, code));
return withWaitLogging("WebSocket.waitForFrameReceived", logger -> waitForFrameReceivedImpl(options, code));
}
private WebSocketFrame waitForFrameReceivedImpl(WaitForFrameReceivedOptions options, Runnable code) {
@@ -99,7 +99,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
@Override
public WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable code) {
return withWaitLogging("WebSocket.waitForFrameSent", () -> waitForFrameSentImpl(options, code));
return withWaitLogging("WebSocket.waitForFrameSent", logger -> waitForFrameSentImpl(options, code));
}
private WebSocketFrame waitForFrameSentImpl(WaitForFrameSentOptions options, Runnable code) {
@@ -59,7 +59,7 @@ class WorkerImpl extends ChannelOwner implements Worker {
@Override
public Worker waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Worker.waitForClose", () -> waitForCloseImpl(options, code));
return withWaitLogging("Worker.waitForClose", logger -> waitForCloseImpl(options, code));
}
private Worker waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
@@ -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
}
@@ -115,6 +115,13 @@ public interface RequestOptions {
* @param ignoreHTTPSErrors Whether to ignore HTTPS errors when sending network requests.
*/
RequestOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors);
/**
*
*
* @param maxRedirects Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects.
*/
RequestOptions setMaxRedirects(int maxRedirects);
/**
* Changes the request method (e.g. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> or <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a>).
@@ -195,6 +195,7 @@ public class Server implements HttpHandler {
String resourcePath = "resources" + path;
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
if (resource == null) {
exchange.getResponseHeaders().add("Content-Type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + resourcePath);
@@ -19,8 +19,11 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.io.OutputStreamWriter;
import java.io.Writer;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestAPIResponseAssertions extends TestBase {
@Test
@@ -38,14 +41,62 @@ public class TestAPIResponseAssertions extends TestBase {
@Test
void fail() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
boolean didThrow = false;
try {
assertThat(res).isOK();
} catch (AssertionFailedError e) {
didThrow = true;
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
}
@Test
void shouldPrintResponseTextIfIdOkFails() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK());
assertTrue(e.getMessage().contains("File not found"), "Actual error: " + e.toString());
}
@Test
void shouldOnlyPrintResponseWithTextContentTypeIfIsOkFails() {
{
server.setRoute("/text-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Text error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/text-content-type")).isOK());
assertTrue(e.getMessage().contains("Text error"), "Actual error: " + e);
}
{
server.setRoute("/svg-xml-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/svg+xml");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Json error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/svg-xml-content-type")).isOK());
assertTrue(e.getMessage().contains("Json error"), "Actual error: " + e);
}
{
server.setRoute("/no-content-type", exchange -> {
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("No content type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/no-content-type")).isOK());
assertFalse(e.getMessage().contains("No content type error"), "Actual error: " + e);
}
{
server.setRoute("/image-content-type", exchange -> {
exchange.getResponseHeaders().set("Content-type", "image/bmp");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Image type error");
}
});
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(page.request().get(server.PREFIX + "/image-content-type")).isOK());
assertFalse(e.getMessage().contains("Image type error"), "Actual error: " + e);
}
assertTrue(didThrow);
}
}
@@ -18,6 +18,8 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.*;
import com.microsoft.playwright.options.SameSiteAttribute;
import java.io.IOException;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
@@ -35,9 +37,11 @@ public class TestBase {
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
static final boolean headful;
static final SameSiteAttribute defaultSameSiteCookieValue;
static {
String headfulEnv = System.getenv("HEADFUL");
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
defaultSameSiteCookieValue = initSameSiteAttribute();
}
// Fields reset before each test.
@@ -145,4 +149,11 @@ public class TestBase {
page = null;
}
}
private static SameSiteAttribute initSameSiteAttribute() {
if (isChromium()) return SameSiteAttribute.LAX;
if (isWebKit()) return SameSiteAttribute.NONE;
// for firefox version >= 103 'None' is used.
return SameSiteAttribute.NONE;
}
}
@@ -48,12 +48,8 @@ public class TestBrowser extends TestBase {
@Test
void shouldThrowUponSecondCreateNewPage() {
Page page = browser.newPage();
try {
page.context().newPage();
fail("newPage should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Please use browser.newContext()"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.context().newPage());
assertTrue(e.getMessage().contains("Please use browser.newContext()"));
page.close();
}
@@ -16,7 +16,6 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.microsoft.playwright.options.Cookie;
import org.junit.jupiter.api.Test;
@@ -27,7 +26,6 @@ import java.util.concurrent.Future;
import static com.microsoft.playwright.Utils.assertJsonEquals;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
@@ -51,15 +49,11 @@ public class TestBrowserContextAddCookies extends TestBase {
"}");
assertEquals("username=John Doe", documentCookie);
List<Cookie> cookies = context.cookies();
assertEquals(1, cookies.size());
context.clearCookies();
assertEquals(emptyList(), context.cookies());
context.addCookies(asList(new Cookie(cookies.get(0).name, cookies.get(0).value)
.setDomain(cookies.get(0).domain)
.setPath(cookies.get(0).path)
.setExpires(cookies.get(0).expires)
.setSameSite(cookies.get(0).sameSite)
));
assertJsonEquals(new Gson().toJson(cookies), context.cookies());
assertEquals(0, context.cookies().size());
context.addCookies(cookies);
assertJsonEquals(cookies, context.cookies());
}
@Test
@@ -248,27 +242,19 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldNotSetACookieWithBlankPageURL() {
try {
context.addCookies(asList(
new Cookie("example-cookie", "best").setUrl(server.EMPTY_PAGE),
new Cookie("example-cookie-blank", "best").setUrl("about:blank")
));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Blank page can not have cookie \"example-cookie-blank\""));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.addCookies(asList(
new Cookie("example-cookie", "best").setUrl(server.EMPTY_PAGE),
new Cookie("example-cookie-blank", "best").setUrl("about:blank")
)));
assertTrue(e.getMessage().contains("Blank page can not have cookie \"example-cookie-blank\""));
}
@Test
void shouldNotSetACookieOnADataURLPage() {
try {
context.addCookies(asList(
new Cookie("example-cookie", "best").setUrl("data:,Hello%2C%20World!")
));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Data URL page can not have cookie \"example-cookie\""));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.addCookies(asList(
new Cookie("example-cookie", "best").setUrl("data:,Hello%2C%20World!")
)));
assertTrue(e.getMessage().contains("Data URL page can not have cookie \"example-cookie\""));
}
@Test
@@ -116,22 +116,18 @@ public class TestBrowserContextBasic extends TestBase {
@Test
void shouldNotAllowDeviceScaleFactorWithNullViewport() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0).setViewportSize(null));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
}
});
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
}
@Test
void shouldNotAllowIsMobileWithNullViewport() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
browser.newContext(new Browser.NewContextOptions().setIsMobile(true).setViewportSize(null));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
}
});
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
}
@Test
@@ -143,12 +139,10 @@ public class TestBrowserContextBasic extends TestBase {
@Test
void closeShouldAbortFutureEvent() {
BrowserContext context = browser.newContext();
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.waitForPage(() -> context.close());
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Context closed"));
}
});
assertTrue(e.getMessage().contains("Context closed"));
}
@Test
@@ -211,15 +205,11 @@ public class TestBrowserContextBasic extends TestBase {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setJavaScriptEnabled(false));
Page page = context.newPage();
page.navigate("data:text/html, <script>var something = 'forbidden'</script>");
try {
page.evaluate("something");
fail("did not throw");
} catch (PlaywrightException e) {
if (isWebKit())
assertTrue(e.getMessage().contains("Can\'t find variable: something"));
else
assertTrue(e.getMessage().contains("something is not defined"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.evaluate("something"));
if (isWebKit())
assertTrue(e.getMessage().contains("Can\'t find variable: something"));
else
assertTrue(e.getMessage().contains("something is not defined"));
context.close();
}
@@ -244,11 +234,7 @@ public class TestBrowserContextBasic extends TestBase {
void shouldWorkWithOfflineOption() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setOffline(true));
Page page = context.newPage();
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
}
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
context.setOffline(false);
Response response = page.navigate(server.EMPTY_PAGE);
@@ -20,7 +20,6 @@ import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.SameSiteAttribute;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.util.Comparator;
import java.util.List;
@@ -29,8 +28,7 @@ import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.assertJsonEquals;
import static com.microsoft.playwright.Utils.getOS;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextCookies extends TestBase {
@Test
@@ -60,31 +58,28 @@ public class TestBrowserContextCookies extends TestBase {
page.navigate(server.EMPTY_PAGE);
// @see https://en.wikipedia.org/wiki/Year_2038_problem
Object documentCookie = page.evaluate("() => {\n" +
" const date= new Date('1/1/2038');\n" +
" document.cookie = `username=John Doe;expires=${date.toUTCString()}`;\n" +
" return document.cookie;\n" +
" }");
" const date = new Date('1/1/2038');\n" +
" document.cookie = `username=John Doe;expires=${date.toUTCString()}`;\n" +
" return document.cookie;\n" +
"}");
assertEquals("username=John Doe", documentCookie);
Cookie cookie = context.cookies().get(0);
assertEquals("username", cookie.name);
assertEquals("John Doe", cookie.value);
assertEquals("localhost", cookie.domain);
assertEquals("/", cookie.path);
List<Cookie> cookies = context.cookies();
assertEquals(1, cookies.size());
assertEquals("username", cookies.get(0).name);
assertEquals("John Doe", cookies.get(0).value);
assertEquals("localhost", cookies.get(0).domain);
assertEquals("/", cookies.get(0).path);
assertFalse(cookies.get(0).httpOnly);
assertFalse(cookies.get(0).secure);
assertEquals(defaultSameSiteCookieValue, cookies.get(0).sameSite);
// Browsers start to cap cookies with 400 days max expires value.
// See https://github.com/httpwg/http-extensions/pull/1732
// Chromium patch: https://chromium.googlesource.com/chromium/src/+/aaa5d2b55478eac2ee642653dcd77a50ac3faff6
// We want to make sure that expires date is at least 400 days in future.
Double timestamp = (Double) page.evaluate("const FOUR_HUNDRED_DAYS = 1000 * 60 * 60 * 24 * 400;\n" +
" const FIVE_MINUTES = 1000 * 60 * 5; // relax condition a bit to make sure test is not flaky.\n" +
" (Date.now() + FOUR_HUNDRED_DAYS - FIVE_MINUTES) / 1000;");
assertTrue(cookie.expires > timestamp, cookie.expires + " > " + timestamp + " failed.");
assertEquals(false, cookie.httpOnly);
assertEquals(false, cookie.secure);
if (isChromium()) {
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
} else {
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
}
int FOUR_HUNDRED_DAYS = 1000 * 60 * 60 * 24 * 400;
int FIVE_MINUTES = 1000 * 60 * 5; // relax condition a bit to make sure test is not flaky.
assertTrue(cookies.get(0).expires > ((System.currentTimeMillis() + FOUR_HUNDRED_DAYS - FIVE_MINUTES) / 1000));
}
@Test
@@ -58,28 +58,16 @@ public class TestBrowserContextExposeFunction extends TestBase {
void shouldThrowForDuplicateRegistrations() {
context.exposeFunction("foo", args -> null);
context.exposeFunction("bar", args -> null);
try {
context.exposeFunction("foo", args -> null);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.exposeFunction("foo", args -> null));
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered"));
Page page = context.newPage();
try {
page.exposeFunction("foo", args -> null);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered in the browser context"));
}
e = assertThrows(PlaywrightException.class, () -> page.exposeFunction("foo", args -> null));
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered in the browser context"));
page.exposeFunction("baz", args -> null);
try {
context.exposeFunction("baz", args -> null);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Function \"baz\" has been already registered in one of the pages"));
}
e = assertThrows(PlaywrightException.class, () -> context.exposeFunction("baz", args -> null));
assertTrue(e.getMessage().contains("Function \"baz\" has been already registered in one of the pages"));
}
@Test
@@ -51,24 +51,16 @@ public class TestBrowserContextFetch extends TestBase {
@Test
void shouldThrowOnNetworkError() {
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
try {
context.request().get(server.PREFIX + "/test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/test"));
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
@Test
void shouldThrowOnNetworkErrorAfterRedirect() {
server.setRedirect("/redirect", "/test");
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
@Test
@@ -80,12 +72,8 @@ public class TestBrowserContextFetch extends TestBase {
writer.write("<title>A");
}
});
try {
context.request().get(server.PREFIX + "/test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/test"));
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
@Test
@@ -98,12 +86,8 @@ public class TestBrowserContextFetch extends TestBase {
writer.write("<title>A");
}
});
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
@Test
@@ -134,12 +118,10 @@ public class TestBrowserContextFetch extends TestBase {
@Test
void getShouldSupportFailOnStatusCode() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.request().get(server.PREFIX + "/does-not-exist.html", RequestOptions.create().setFailOnStatusCode(true));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage());
}
@Test
@@ -305,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 {
@@ -385,29 +388,20 @@ public class TestBrowserContextFetch extends TestBase {
@Test
void shouldThrowOnInvalidHeaderValue() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.request().get(server.EMPTY_PAGE, RequestOptions.create()
.setHeader("foo", "недопустимое значение"));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage());
}
@Test
void shouldThrowOnNonHttpSProtocol() {
try {
context.request().get("data:text/plain,test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Protocol \"data:\" not supported"), e.getMessage());
}
try {
context.request().get("file:///tmp/foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Protocol \"file:\" not supported"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get("data:text/plain,test"));
assertTrue(e.getMessage().contains("Protocol \"data:\" not supported"), e.getMessage());
e = assertThrows(PlaywrightException.class, () -> context.request().get("file:///tmp/foo"));
assertTrue(e.getMessage().contains("Protocol \"file:\" not supported"), e.getMessage());
}
@Test
@@ -417,12 +411,10 @@ public class TestBrowserContextFetch extends TestBase {
exchange.sendResponseHeaders(200, 4096);
});
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.request().get(server.PREFIX + "/slow", RequestOptions.create().setTimeout(100));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
@Test
@@ -453,12 +445,8 @@ public class TestBrowserContextFetch extends TestBase {
});
context.setDefaultTimeout(100);
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
@Test
@@ -466,12 +454,8 @@ public class TestBrowserContextFetch extends TestBase {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
response.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
@Test
@@ -479,13 +463,9 @@ public class TestBrowserContextFetch extends TestBase {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
context.close();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed") ||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
assertTrue(e.getMessage().contains("Response has been disposed") ||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
}
@Test
void shouldOverrideRequestParameters() throws ExecutionException, InterruptedException {
@@ -614,17 +594,11 @@ public class TestBrowserContextFetch extends TestBase {
}
@Test
void shouldThrowWhenDataPassedForUnsupportedRequest() {
try {
context.request().fetch(server.EMPTY_PAGE, RequestOptions.create()
.setMethod("GET").setData("bar"));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Method GET does not accept post data"), e.getMessage());
}
void shouldNotThrowWhenDataPassedForUnsupportedRequest() {
context.request().fetch(server.EMPTY_PAGE, RequestOptions.create()
.setMethod("GET").setData("bar"));
}
@Test
void contextRequestShouldExportSameStorageStateAsContext() {
server.setRoute("/setcookie.html", exchange -> {
@@ -666,18 +640,10 @@ public class TestBrowserContextFetch extends TestBase {
return null;
});
page.evaluate("() => setTimeout(closeContext, 1000);");
try {
context.request().get(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
try {
context.request().post(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
e = assertThrows(PlaywrightException.class, () -> context.request().post(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
}
@@ -79,11 +79,7 @@ public class TestBrowserContextHar extends TestBase {
Path path = Paths.get("src/test/resources/har-fulfill.har");
context.routeFromHAR(path);
Page page = context.newPage();
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
}
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
}
@Test
@@ -350,11 +346,7 @@ public class TestBrowserContextHar extends TestBase {
assertEquals("2", page2.evaluate(fetchFunction, "2"));
assertEquals("3", page2.evaluate(fetchFunction, "3"));
assertEquals("3", page2.evaluate(fetchFunction, "3"));
try {
page2.evaluate(fetchFunction, "4");
fail("did not throw");
} catch (PlaywrightException e) {
}
assertThrows(PlaywrightException.class, () -> page2.evaluate(fetchFunction, "4"));
}
}
@@ -47,9 +47,9 @@ public class TestBrowserContextProxy extends TestBase {
@EnabledIf(value="isChromiumWindows", disabledReason="Platform-specific")
void shouldThrowForMissingGlobalProxyOnChromiumWindows() {
try (Browser browser = browserType.launch(createLaunchOptions())) {
browser.newContext(new Browser.NewContextOptions().setProxy("localhost:" + server.PORT));
fail("did not throw");
} catch (PlaywrightException e) {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
browser.newContext(new Browser.NewContextOptions().setProxy("localhost:" + server.PORT));
});
assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy"));
}
}
@@ -179,23 +179,9 @@ public class TestBrowserContextProxy extends TestBase {
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
try {
page.navigate("http://1.non.existent.domain.for.the.test/target.html");
fail("did not throw");
} catch (PlaywrightException exception) {
}
try {
page.navigate("http://2.non.existent.domain.for.the.test/target.html");
fail("did not throw");
} catch (PlaywrightException e) {
}
try {
page.navigate("http://foo.is.the.another.test/target.html");
fail("did not throw");
} catch (PlaywrightException e) {
}
assertThrows(PlaywrightException.class, () -> page.navigate("http://1.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://2.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://foo.is.the.another.test/target.html"));
page.navigate("http://3.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
@@ -169,12 +169,8 @@ public class TestBrowserContextRoute extends TestBase {
throw new RuntimeException("My Exception");
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("My Exception"), e.getMessage());
}
RuntimeException e = assertThrows(RuntimeException.class, () -> page.navigate(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("My Exception"), e.getMessage());
}
@Test
@@ -187,12 +183,8 @@ public class TestBrowserContextRoute extends TestBase {
// Fulfilling with dsiposed response will lead to a server-side exception.
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Fetch response has been disposed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Fetch response has been disposed"), e.getMessage());
}
@Test
@@ -201,12 +193,8 @@ public class TestBrowserContextRoute extends TestBase {
page.route("**/*", route -> {
route.resume(new Route.ResumeOptions().setUrl("file:///tmp"));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage());
}
@@ -260,12 +248,7 @@ public class TestBrowserContextRoute extends TestBase {
route.fallback();
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertNotNull(e);
}
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
assertFalse(failed[0]);
}
@@ -38,12 +38,8 @@ public class TestBrowserContextStrict extends TestBase {
@Test
void shouldFailPageTextContentInStrictMode() {
page.setContent("<span>span1</span><div><span>target</span></div>");
try {
page.textContent("span");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("strict mode violation"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.textContent("span"));
assertTrue(e.getMessage().contains("strict mode violation"));
}
@Test
@@ -43,11 +43,7 @@ public class TestBrowserTypeBasic extends TestBase {
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Non-chromium behavior")
void shouldThrowWhenTryingToConnectWithNotChromium() {
try {
browserType.connectOverCDP("foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> browserType.connectOverCDP("foo"));
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
}
}
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.driver.Driver;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
@@ -60,8 +60,8 @@ public class TestBrowserTypeConnect extends TestBase {
private static BrowserServer launchBrowserServer(BrowserType browserType) {
try {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.getParent();
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.driverPath().getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
// We launch node process directly instead of using playwright.sh script as killing the script
@@ -192,12 +192,8 @@ public class TestBrowserTypeConnect extends TestBase {
remote.kill();
assertEquals(1, disconnected1[0]);
try {
// Tickle connection so that it gets a chance to dispatch disconnect event.
page2.title();
fail("did not throw");
} catch (PlaywrightException e) {
}
// Tickle connection so that it gets a chance to dispatch disconnect event.
assertThrows(PlaywrightException.class, () -> page2.title());
assertEquals(1, disconnected2[0]);
}
@@ -238,11 +234,8 @@ public class TestBrowserTypeConnect extends TestBase {
}
}
assertFalse(remote.isConnected());
try {
page.evaluate("1 + 1");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser has been closed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.evaluate("1 + 1"));
assertTrue(e.getMessage().contains("Browser has been closed"), e.getMessage());
assertFalse(remote.isConnected());
}
@@ -262,12 +255,8 @@ public class TestBrowserTypeConnect extends TestBase {
}
}
assertFalse(browser.isConnected());
try {
page.waitForNavigation(() -> {});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForNavigation(() -> {}));
assertTrue(e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
}
@Test
@@ -277,12 +266,10 @@ public class TestBrowserTypeConnect extends TestBase {
server.setRoute("/one-style.css", r -> {});
page.onRequest(r -> remote.close());
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.navigate(server.PREFIX + "/one-style.html", new Page.NavigateOptions().setTimeout(60000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser has been closed"));
}
});
assertTrue(e.getMessage().contains("Browser has been closed"));
}
@Test
@@ -408,12 +395,8 @@ public class TestBrowserTypeConnect extends TestBase {
Path savedAsPath = tempDir.resolve("my-video.webm");
page.video().saveAs(savedAsPath);
assertTrue(Files.exists(savedAsPath));
try {
page.video().path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use saveAs() to save a local copy."));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.video().path());
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use saveAs() to save a local copy."));
}
@@ -435,12 +418,8 @@ public class TestBrowserTypeConnect extends TestBase {
download.saveAs(nestedPath);
assertTrue(Files.exists(nestedPath));
assertEquals("Hello world", new String(Files.readAllBytes(nestedPath), StandardCharsets.UTF_8));
try {
download.path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy."));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> download.path());
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy."));
page.close();
}
@@ -459,12 +438,8 @@ public class TestBrowserTypeConnect extends TestBase {
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = tempDir.resolve("download.txt");
download.delete();
try {
download.saveAs(userPath);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> download.saveAs(userPath));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"));
page.close();
}
@@ -80,12 +80,10 @@ public class TestChromiumTracing extends TestBase {
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
Page newPage = browser.newPage();
try {
assertThrows(PlaywrightException.class, () -> {
browser.startTracing(newPage, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
fail("did not throw");
} catch (PlaywrightException e) {
}
});
newPage.close();
browser.stopTracing();
}
@@ -172,14 +172,10 @@ public class TestClick extends TestBase {
void shouldNotWaitWithForce() {
page.navigate(server.PREFIX + "/input/button.html");
page.evalOnSelector("button", "b => b.style.display = 'none'");
Exception exception = null;
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.click("button", new Page.ClickOptions().setForce(true));
} catch (PlaywrightException e) {
exception = e;
}
assertNotNull(exception);
assertTrue(exception.getMessage().contains("Element is not visible"));
});
assertTrue(e.getMessage().contains("Element is not visible"));
assertEquals("Was not clicked", page.evaluate("result"));
}
@@ -428,8 +424,8 @@ public class TestClick extends TestBase {
int expectedY = 18;
if (isWebKit()) {
// WebKit rounds up during css -> dip -> css conversion.
expectedX = 29;
expectedY = 19;
expectedX = 26;
expectedY = 17;
} else if (isChromium() && !headful) {
// Headless Chromium rounds down during css -> dip -> css conversion.
expectedX = 27;
@@ -564,12 +560,10 @@ public class TestClick extends TestBase {
page.evaluate("addButton()");
ElementHandle handle = page.querySelector("button");
page.evaluate("stopButton(true)");
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
handle.click(new ElementHandle.ClickOptions().setForce(true));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
});
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
assertEquals(null, page.evaluate("window.clicked"));
}
@@ -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,13 +193,11 @@ 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-");
try {
Path userDataDir = tempDir.resolve("user-data-dir");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
browserType.launchPersistentContext(userDataDir, options);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("can not specify page"));
}
});
assertTrue(e.getMessage().contains("can not specify page"));
}
@Test
@@ -256,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());
}}
@@ -102,9 +102,8 @@ public class TestDownload extends TestBase {
assertTrue(error[0].getMessage().contains("Download is starting"));
assertEquals("about:blank", page.url());
} else {
assertNotNull(response[0]);
assertEquals(200, response[0].status());
assertEquals(server.PREFIX + "/download", page.url());
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("Download is starting"));
}
page.close();
}
@@ -116,13 +115,9 @@ public class TestDownload extends TestBase {
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals(server.PREFIX + "/downloadWithFilename", download.url());
assertEquals("file.txt", download.suggestedFilename());
try {
download.path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(download.failure().contains("acceptDownloads"));
assertTrue(e.getMessage().contains("acceptDownloads: true"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> download.path());
assertTrue(download.failure().contains("acceptDownloads"));
assertTrue(e.getMessage().contains("acceptDownloads: true"));
}
}
@Test
@@ -243,12 +238,8 @@ public class TestDownload extends TestBase {
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = Files.createTempFile("download-", ".txt");
try {
download.saveAs(userPath);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Pass { acceptDownloads: true } when you are creating your browser context"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> download.saveAs(userPath));
assertTrue(e.getMessage().contains("Pass { acceptDownloads: true } when you are creating your browser context"));
page.close();
}
@@ -260,12 +251,8 @@ public class TestDownload extends TestBase {
Path userPath = Files.createTempFile("download-", ".txt");
download.delete();
try {
download.saveAs(userPath);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> download.saveAs(userPath));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
page.close();
}
@@ -60,12 +60,8 @@ public class TestElementHandleClick extends TestBase {
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
page.evaluate("button => button.remove()", button);
try {
button.click();
fail("click should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> button.click());
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
@Test
@@ -73,12 +69,10 @@ public class TestElementHandleClick extends TestBase {
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
page.evaluate("button => button.style.display = 'none'", button);
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
button.click(new ElementHandle.ClickOptions().setForce(true));
fail("click should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not visible"));
}
});
assertTrue(e.getMessage().contains("Element is not visible"));
}
@Test
@@ -86,24 +80,20 @@ public class TestElementHandleClick extends TestBase {
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
page.evaluate("button => button.parentElement.style.display = 'none'", button);
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
button.click(new ElementHandle.ClickOptions().setForce(true));
fail("click should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not visible"));
}
});
assertTrue(e.getMessage().contains("Element is not visible"));
}
@Test
void shouldThrowForBrElementsWithForce() {
page.setContent("hello<br>goodbye");
ElementHandle br = page.querySelector("br");
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
br.click(new ElementHandle.ClickOptions().setForce(true));
fail("click should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is outside of the viewport"));
}
});
assertTrue(e.getMessage().contains("Element is outside of the viewport"));
}
@Test
@@ -57,19 +57,12 @@ public class TestElementHandleConvenience extends TestBase {
ElementHandle handle = page.querySelector("#input");
assertEquals("input value", handle.inputValue());
try {
page.inputValue("#inner");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.inputValue("#inner"));
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
ElementHandle handle2 = page.querySelector("#inner");
try {
handle2.inputValue();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
e = assertThrows(PlaywrightException.class, () -> handle2.inputValue());
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
@Test
@@ -91,19 +84,11 @@ public class TestElementHandleConvenience extends TestBase {
@Test
void innerTextShouldThrow() {
page.setContent("<svg>text</svg>");
try {
page.innerText("svg");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.innerText("svg"));
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
ElementHandle handle = page.querySelector("svg");
try {
handle.innerText();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
e = assertThrows(PlaywrightException.class, () -> handle.innerText());
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
@Test
@@ -267,11 +252,7 @@ public class TestElementHandleConvenience extends TestBase {
handle.evaluate("input => input.checked = false");
assertFalse(handle.isChecked());
assertFalse(page.isChecked("input"));
try {
page.isChecked("div");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.isChecked("div"));
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
}
@@ -47,12 +47,10 @@ public class TestElementHandleSelectText extends TestBase {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle textarea = page.querySelector("textarea");
textarea.evaluate("e => e.style.display = 'none'");
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
textarea.selectText(new ElementHandle.SelectTextOptions().setTimeout(3000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("element is not visible"));
}
});
assertTrue(e.getMessage().contains("element is not visible"));
}
// @Test
@@ -20,8 +20,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import static com.microsoft.playwright.options.ElementState.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleWaitForElementState extends TestBase {
@@ -51,12 +50,10 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldTimeoutWaitingForVisible() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
div.waitForElementState(VISIBLE, new ElementHandle.WaitForElementStateOptions().setTimeout(1000));
fail("did not throw");
} catch (TimeoutError e) {
assertTrue(e.getMessage().contains("Timeout 1000ms exceeded"));
}
});
assertTrue(e.getMessage().contains("Timeout 1000ms exceeded"));
}
@Test
@@ -64,12 +61,8 @@ public class TestElementHandleWaitForElementState extends TestBase {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
div.evaluate("div => div.remove()");
try {
div.waitForElementState(VISIBLE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> div.waitForElementState(VISIBLE));
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
@Test
@@ -111,12 +104,8 @@ public class TestElementHandleWaitForElementState extends TestBase {
page.setContent("<button disabled>Target</button>");
ElementHandle button = page.querySelector("button");
button.evaluate("button => button.remove()");
try {
button.waitForElementState(ENABLED);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> button.waitForElementState(ENABLED));
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
@Test
@@ -119,12 +119,10 @@ public class TestEvalOnSelector extends TestBase {
@Test
void shouldThrowErrorIfNoElementIsFound() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.evalOnSelector("section", "e => e.id");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("failed to find element matching selector \"section\""));
}
});
assertTrue(e.getMessage().contains("failed to find element matching selector \"section\""));
}
@Test
@@ -166,23 +164,20 @@ public class TestEvalOnSelector extends TestBase {
@Test
void shouldThrowOnMultipleCaptures() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.evalOnSelector("*css=div >> *css=span", "e => e.outerHTML");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Only one of the selectors can capture using * modifier"));
}
});
assertTrue(e.getMessage().contains("Only one of the selectors can capture using * modifier"));
}
@Test
void shouldThrowOnMalformedCapture() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.evalOnSelector("*=div", "e => e.outerHTML");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Unknown engine \"\" while parsing selector *=div"));
}
});
assertTrue(e.getMessage().contains("Unknown engine \"\" while parsing selector *=div"));
}
@Test
void shouldWorkWithSpacesInCssAttributes() {
page.setContent("<div><input placeholder='Select date'></div>");
@@ -21,8 +21,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;
public class TestFirefoxLauncher extends TestBase {
@@ -47,11 +46,7 @@ public class TestFirefoxLauncher extends TestBase {
"network.proxy.http_port", 3333));
launchBrowser(options);
Page page = browser.newPage();
try {
page.navigate("http://example.com");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate("http://example.com"));
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
}
@@ -19,8 +19,7 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.options.WaitUntilState.NETWORKIDLE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestFrameNavigate extends TestBase {
@@ -43,12 +42,11 @@ public class TestFrameNavigate extends TestBase {
void shouldContinueAfterClientRedirect() {
server.setRoute("/frames/script.js", (httpExchange) -> {});
String url = server.PREFIX + "/frames/child-redirect.html";
try {
TimeoutError e = assertThrows(TimeoutError.class, () -> {
page.navigate(url, new Page.NavigateOptions().setTimeout(5000).setWaitUntil(NETWORKIDLE));
} catch (TimeoutError e) {
assertTrue(e.getMessage().contains("Timeout 5000ms exceeded."));
assertTrue(e.getMessage().contains("navigating to \"" + url +"\", waiting until \"networkidle\""));
}
});
assertTrue(e.getMessage().contains("Timeout 5000ms exceeded."));
assertTrue(e.getMessage().contains("navigating to \"" + url +"\", waiting until \"networkidle\""));
}
// TODO: not supported in sync api
@@ -24,8 +24,7 @@ import java.util.List;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestGeolocation extends TestBase {
@Test
@@ -41,11 +40,10 @@ public class TestGeolocation extends TestBase {
@Test
void shouldThrowWhenInvalidLongitude() {
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.setGeolocation(new Geolocation(10, 200));
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed."));
}
});
assertTrue(e.getMessage().contains("geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed."));
}
@Test
@@ -6,6 +6,8 @@ import com.microsoft.playwright.options.RequestOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -146,12 +148,8 @@ public class TestGlobalFetch extends TestBase {
APIResponse response = request.get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
request.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
@Test
@@ -168,12 +166,8 @@ public class TestGlobalFetch extends TestBase {
void shouldSupportGlobalTimeoutOption() {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setTimeout(1));
server.setRoute("/empty.html", exchange -> {});
try {
request.get(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 1ms"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> request.get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Request timed out after 1ms"), e.getMessage());
}
@@ -265,12 +259,8 @@ public class TestGlobalFetch extends TestBase {
assertEquals(0, body.length);
assertEquals("", response.text());
request.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
@Test
@@ -321,4 +311,73 @@ public class TestGlobalFetch extends TestBase {
}
request.dispose();
}
@Test
void shouldReturnBodyForFailingRequests() {
APIRequestContext request = playwright.request().newContext();
for (String method : new String[] {"head", "put", "trace"}) {
server.setRoute("/empty.html", exchange -> {
exchange.getResponseHeaders().set("Content-type", "text/plain");
exchange.sendResponseHeaders(404, 10);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Not found.");
}
});
APIResponse response = request.fetch(server.EMPTY_PAGE, RequestOptions.create().setMethod(method));
assertEquals(404, response.status());
// HEAD response returns empty body in node http module.
assertEquals("head".equals(method) ? "" : "Not found.", response.text());
}
request.dispose();
}
@Test
void shouldThrowAnErrorWhenMaxRedirectsIsExceeded() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/b/c/redirect3");
server.setRedirect("/b/c/redirect3", "/b/c/redirect4");
server.setRedirect("/b/c/redirect4", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
for (int maxRedirects = 1; maxRedirects < 4; maxRedirects++) {
int currMaxRedirects = maxRedirects;
PlaywrightException exception = assertThrows(PlaywrightException.class,
() -> request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(currMaxRedirects)));
assertTrue(exception.getMessage().contains("Max redirect count exceeded"), exception.getMessage());
}
}
request.dispose();
}
@Test
void shouldNotFollowRedirectsWhenMaxRedirectsIsSetTo0() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
APIResponse response = request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(0));
assertEquals("/b/c/redirect2", response.headers().get("location"));
assertEquals(302, response.status());
}
request.dispose();
}
@Test
void shouldThrowAnErrorWhenMaxRedirectsIsLessThan0() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
PlaywrightException exception = assertThrows(PlaywrightException.class,
() -> request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(-1)));
assertTrue(exception.getMessage().contains("'maxRedirects' should be greater than or equal to '0'"), exception.getMessage());
}
request.dispose();
}
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.assertions.PlaywrightAssertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
@@ -62,14 +63,12 @@ public class TestLocatorAssertions extends TestBase {
void containsTextWRegexFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).containsText(Pattern.compile("ex2"), new LocatorAssertions.ContainsTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("ex2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to contain regex"), e.getMessage());
}
});
assertEquals("ex2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to contain regex"), e.getMessage());
}
@Test
@@ -111,14 +110,12 @@ public class TestLocatorAssertions extends TestBase {
void hasTextWRegexFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasText(Pattern.compile("Text 2"), new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text 2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text matching regex"), e.getMessage());
}
});
assertEquals("Text 2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text matching regex"), e.getMessage());
}
@Test
@@ -138,14 +135,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
// Should normalize whitespace.
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasText("Text", new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
});
assertEquals("Text", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
@Test
@@ -194,14 +189,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<div></div>");
Locator locator = page.locator("p");
// Should normalize whitespace.
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().hasText(new String[] {}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[]", e.getExpected().getStringRepresentation());
assertEquals("null", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage());
}
});
assertEquals("[]", e.getExpected().getStringRepresentation());
assertEquals("null", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage());
}
@Test
@@ -222,16 +215,14 @@ public class TestLocatorAssertions extends TestBase {
page.evaluate("setTimeout(() => {\n" +
" div.innerHTML = \"<p>Text 1</p><p>Text 2</p>\";\n" +
"}, 100);");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {"Text 1", "Text 3", "Extra"}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage());
}
});
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage());
}
@Test
@@ -246,15 +237,13 @@ public class TestLocatorAssertions extends TestBase {
void hasTextWRegExArrayFail() {
page.setContent("<div>Text 1</div><div>Text 3</div>");
Locator locator = page.locator("div");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
// Should normalize whitespace.
assertThat(locator).hasText(new Pattern[] {Pattern.compile( "Text 1"), Pattern.compile("Text \\d"), Pattern.compile("Extra")}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[Text 1, Text \\d, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
});
assertEquals("[Text 1, Text \\d, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
@Test
@@ -268,14 +257,12 @@ public class TestLocatorAssertions extends TestBase {
void hasAttributeTextFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasAttribute("id", "foo", new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage());
}
});
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage());
}
@Test
@@ -289,14 +276,12 @@ public class TestLocatorAssertions extends TestBase {
void hasAttributeRegExpFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasAttribute("id", Pattern.compile(".Nod.."), new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage());
}
});
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage());
}
@Test
@@ -310,14 +295,12 @@ public class TestLocatorAssertions extends TestBase {
void hasClassTextFail() {
page.setContent("<div class=\"bar baz\"></div>");
Locator locator = page.locator("div");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasClass("foo bar baz", new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo bar baz", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
});
assertEquals("foo bar baz", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
@Test
@@ -331,14 +314,12 @@ public class TestLocatorAssertions extends TestBase {
void hasClassRegExpFail() {
page.setContent("<div class=\"bar baz\"></div>");
Locator locator = page.locator("div");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasClass(Pattern.compile("foo Z.*"), new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo Z.*", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
});
assertEquals("foo Z.*", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
@Test
@@ -352,14 +333,12 @@ public class TestLocatorAssertions extends TestBase {
void hasClassTextArrayFail() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasClass(new String[] {"foo", "bar", "missing"}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[foo, bar, missing]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
});
assertEquals("[foo, bar, missing]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
@Test
@@ -373,14 +352,12 @@ public class TestLocatorAssertions extends TestBase {
void hasClassRegExpArrayFail() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasClass(new Pattern[] {Pattern.compile("fo.*"), Pattern.compile(".ar"), Pattern.compile("baz"), Pattern.compile("extra")}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[fo.*, .ar, baz, extra]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
});
assertEquals("[fo.*, .ar, baz, extra]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
@Test
@@ -394,14 +371,12 @@ public class TestLocatorAssertions extends TestBase {
void hasCountFail() {
page.setContent("<select><option>One</option><option>Two</option></select>");
Locator locator = page.locator("option");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasCount(1, new LocatorAssertions.HasCountOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have count"), e.getMessage());
}
});
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have count"), e.getMessage());
}
@Test
@@ -423,14 +398,12 @@ public class TestLocatorAssertions extends TestBase {
void hasCSSFail() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasCSS("color", "red", new LocatorAssertions.HasCSSOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color'"), e.getMessage());
}
});
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color'"), e.getMessage());
}
@Test
@@ -444,14 +417,12 @@ public class TestLocatorAssertions extends TestBase {
void hasCSSRegExFail() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasCSS("color", Pattern.compile("red"), new LocatorAssertions.HasCSSOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color' matching regex"), e.getMessage());
}
});
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color' matching regex"), e.getMessage());
}
@Test
@@ -465,14 +436,12 @@ public class TestLocatorAssertions extends TestBase {
void hasIdFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasId("foo", new LocatorAssertions.HasIdOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have ID"), e.getMessage());
}
});
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have ID"), e.getMessage());
}
@Test
@@ -488,14 +457,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
page.evalOnSelector("div", "e => e.foo = 2021");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasJSProperty("foo", 1, new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2021", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
});
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2021", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
@Test
@@ -503,28 +470,24 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
page.evalOnSelector("div", "e => e.foo = { a: 1, b: 'string' }");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasJSProperty("foo", mapOf("a", 2), new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("{a=2}", e.getExpected().getStringRepresentation());
assertEquals("{a=1, b=string}", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
});
assertEquals("{a=2}", e.getExpected().getStringRepresentation());
assertEquals("{a=1, b=string}", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
@Test
void hasJSPropertyStringFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasJSProperty("id", "foo", new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'id'"), e.getMessage());
}
});
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'id'"), e.getMessage());
}
@Test
@@ -540,14 +503,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValue("Text2", new LocatorAssertions.HasValueOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value"), e.getMessage());
}
});
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value"), e.getMessage());
}
@Test
@@ -571,14 +532,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValue(Pattern.compile("Text2"), new LocatorAssertions.HasValueOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value matching regex"), e.getMessage());
}
});
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value matching regex"), e.getMessage());
}
@Test
@@ -614,14 +573,12 @@ public class TestLocatorAssertions extends TestBase {
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"RR", "GG"});
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValues(new String[]{"R", "G"}, new LocatorAssertions.HasValuesOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[RR, GG]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values"), e.getMessage());
}
});
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[RR, GG]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values"), e.getMessage());
}
@Test
@@ -645,14 +602,12 @@ public class TestLocatorAssertions extends TestBase {
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"B"}, new Locator.SelectOptionOptions().setTimeout(1000));
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[B]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values matching regex"), e.getMessage());
}
});
assertEquals("[R, G]", e.getExpected().getStringRepresentation());
assertEquals("[B]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have values matching regex"), e.getMessage());
}
@Test
@@ -664,24 +619,20 @@ public class TestLocatorAssertions extends TestBase {
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"B"});
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
@Test
void hasValuesFailsWhenNotASelectElement() {
page.setContent("<input value=\"foo\" />");
Locator locator = page.locator("input");
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")}, new LocatorAssertions.HasValuesOptions().setTimeout(1000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
}
@Test
@@ -695,28 +646,24 @@ public class TestLocatorAssertions extends TestBase {
void isCheckedFail() {
page.setContent("<input type=checkbox></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be checked"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be checked"), e.getMessage());
}
@Test
void notIsCheckedFail() {
page.setContent("<input type=checkbox checked></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be checked"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be checked"), e.getMessage());
}
@Test
@@ -737,28 +684,24 @@ public class TestLocatorAssertions extends TestBase {
void isDisabledFail() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be disabled"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be disabled"), e.getMessage());
}
@Test
void notIsDisabledFail() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be disabled"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be disabled"), e.getMessage());
}
@Test
@@ -772,30 +715,53 @@ public class TestLocatorAssertions extends TestBase {
void isEditableFail() {
page.setContent("<input disabled></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be editable"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be editable"), e.getMessage());
}
@Test
void notIsEditableFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be editable"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be editable"), e.getMessage());
}
@Test
void isEditableWithNot() {
page.setContent("<input readonly></input>");
Locator locator = page.locator("input");
assertThat(locator).not().isEditable();
}
@Test
void isEditableWithEditableTrue() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
assertThat(locator).isEditable(new LocatorAssertions.IsEditableOptions().setEditable(true));
}
@Test
void isEditableWithEditableFalse() {
page.setContent("<input readonly></input>");
Locator locator = page.locator("input");
assertThat(locator).isEditable(new LocatorAssertions.IsEditableOptions().setEditable(false));
}
@Test
void isEditableWithNotAndEditableFalse() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
assertThat(locator).not().isEditable(new LocatorAssertions.IsEditableOptions().setEditable(false));
}
@Test
void isEmptyPass() {
@@ -808,28 +774,24 @@ public class TestLocatorAssertions extends TestBase {
void isEmptyFail() {
page.setContent("<input value=text></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be empty"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be empty"), e.getMessage());
}
@Test
void notIsEmptyFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be empty"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be empty"), e.getMessage());
}
@Test
@@ -843,28 +805,65 @@ public class TestLocatorAssertions extends TestBase {
void isEnabledFail() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be enabled"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be enabled"), e.getMessage());
}
@Test
void notIsEnabledFail() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be enabled"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be enabled"), e.getMessage());
}
@Test
void isEnabledTrue() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
assertThat(locator).isEnabled(new LocatorAssertions.IsEnabledOptions().setEnabled(true));
}
@Test
void isEnabledFalse() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
assertThat(locator).isEnabled(new LocatorAssertions.IsEnabledOptions().setEnabled(false));
}
@Test
void isEnabledEventually() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
locator.evaluate("e => setTimeout(() => {\n" +
" e.removeAttribute('disabled');\n" +
"}, 500);\n");
assertThat(locator).isEnabled();
}
@Test
void isEnabledEventuallyWithNot() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
locator.evaluate("e => setTimeout(() => {\n" +
" e.setAttribute('disabled', '');\n" +
"}, 500);\n");
assertThat(locator).not().isEnabled();
}
@Test
void isEnabledWithNotAndEnabledFalse() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
assertThat(locator).not().isEnabled(new LocatorAssertions.IsEnabledOptions().setEnabled(false));
}
@Test
@@ -879,14 +878,12 @@ public class TestLocatorAssertions extends TestBase {
void isFocusedFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be focused"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be focused"), e.getMessage());
}
@Test
@@ -894,14 +891,12 @@ public class TestLocatorAssertions extends TestBase {
page.setContent("<input></input>");
Locator locator = page.locator("input");
locator.focus();
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be focused"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be focused"), e.getMessage());
}
@Test
@@ -915,28 +910,24 @@ public class TestLocatorAssertions extends TestBase {
void isHiddenFail() {
page.setContent("<button></button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be hidden"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be hidden"), e.getMessage());
}
@Test
void notIsHiddenFail() {
page.setContent("<button style='display: none'></button>");
Locator locator = page.locator("button");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be hidden"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be hidden"), e.getMessage());
}
@Test
@@ -950,28 +941,65 @@ public class TestLocatorAssertions extends TestBase {
void isVisibleFail() {
page.setContent("<input style='display: none'></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be visible"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be visible"), e.getMessage());
}
@Test
void notIsVisibleFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).not().isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be visible"), e.getMessage());
}
});
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be visible"), e.getMessage());
}
@Test
void isVisibleWithTrue() {
page.setContent("<button>hello</button>");
Locator locator = page.locator("button");
assertThat(locator).isVisible(new LocatorAssertions.IsVisibleOptions().setVisible(true));
}
@Test
void isVisibleWithFalse() {
page.setContent("<button hidden>hello</button>");
Locator locator = page.locator("button");
assertThat(locator).isVisible(new LocatorAssertions.IsVisibleOptions().setVisible(false));
}
@Test
void isVisibleWithNotAndFalse() {
page.setContent("<button>hello</button>");
Locator locator = page.locator("button");
assertThat(locator).not().isVisible(new LocatorAssertions.IsVisibleOptions().setVisible(false));
}
@Test
void isVisibleEventually() {
page.setContent("<div></div>");
Locator locator = page.locator("span");
page.evalOnSelector("div", "div => setTimeout(() => {\n" +
" div.innerHTML = '<span>Hello</span>';\n" +
" }, 10);");
assertThat(locator).isVisible();
}
@Test
void isVisibleEventuallyWithNot() {
page.setContent("<div><span>Hello</span></div>");
Locator locator = page.locator("span");
page.evalOnSelector("span", "span => setTimeout(() => {\n" +
" span.textContent = '';\n" +
" }, 10);");
assertThat(locator).not().isVisible();
}
@Test
@@ -980,4 +1008,35 @@ public class TestLocatorAssertions extends TestBase {
page.locator("#searchResultTableDiv .x-grid3-row").count();
assertThat(page.locator("#searchResultTableDiv .x-grid3-row")).hasCount(0);
}
@Test
void defaultTimeoutHasTextFail() {
page.setContent("<div></div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
assertTrue(exception.getMessage().contains("Locator.expect with timeout 1000ms"), exception.getMessage());
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void defaultTimeoutZeroHasTextPass() {
page.setContent("<div>foo</div>");
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(0);
assertThat(locator).hasText("foo");
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
}
@@ -61,19 +61,13 @@ public class TestLocatorConvenience extends TestBase {
Locator locator = page.locator("#input");
assertEquals("input value", locator.inputValue());
try {
page.inputValue("#inner");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.inputValue("#inner"));
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
e = assertThrows(PlaywrightException.class, () -> {
Locator locator2 = page.locator("#inner");
locator2.inputValue();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
});
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
}
@Test
@@ -95,19 +89,12 @@ public class TestLocatorConvenience extends TestBase {
@Test
void innerTextShouldThrow() {
page.setContent("<svg>text</svg>");
try {
page.innerText("svg");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.innerText("svg"));
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
Locator locator = page.locator("svg");
try {
locator.innerText();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
e = assertThrows(PlaywrightException.class, () -> locator.innerText());
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
}
@Test
@@ -184,12 +171,8 @@ public class TestLocatorConvenience extends TestBase {
element.evaluate("input => input.checked = false");
assertFalse(element.isChecked());
assertFalse(page.isChecked("input"));
try {
page.isChecked("div");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.isChecked("div"));
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
@Test
@@ -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"));
@@ -99,12 +101,10 @@ public class TestLocatorFrame extends TestBase {
@Test
void shouldWaitForFrame() {
page.navigate(server.EMPTY_PAGE);
try {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage());
}
});
assertTrue(e.getMessage().contains("waiting for frameLocator(\"iframe\")"), e.getMessage());
}
@Test
@@ -206,13 +206,9 @@ public class TestLocatorFrame extends TestBase {
routeIframe(page);
page.setContent("<div></div>");
Locator button = page.frameLocator("div").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> button.waitFor());
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
}
@Test
@@ -231,12 +227,8 @@ public class TestLocatorFrame extends TestBase {
routeAmbiguous(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> button.waitFor());
assertTrue(e.getMessage().contains("Error: strict mode violation: locator(\"body\").locator(\"iframe\") resolved to 3 elements"), e.getMessage());
}
@Test
@@ -251,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("");
}
}

Some files were not shown because too many files have changed in this diff Show More