Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc07c91276 | |||
| 6ae0df89fa | |||
| 5faf1b028a | |||
| 0212ebe3df | |||
| eee7732f1f |
+2
-4
@@ -1,5 +1,3 @@
|
||||
# text files must be lf for golden file tests to work
|
||||
* text=auto eol=lf
|
||||
|
||||
# make project show as TS on GitHub
|
||||
*.js linguist-detectable=false
|
||||
*.txt eol=lf
|
||||
*.json eol=lf
|
||||
|
||||
@@ -3,11 +3,6 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
is_release:
|
||||
required: true
|
||||
type: boolean
|
||||
description: "Is this a release image?"
|
||||
branches:
|
||||
- release-*
|
||||
jobs:
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: "Internal Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: "trigger"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: |
|
||||
curl -X POST \
|
||||
-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
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
|
||||
@@ -20,7 +20,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- name: Download drivers
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Regenerate APIs
|
||||
|
||||
+1
-14
@@ -8,7 +8,7 @@ Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubun
|
||||
just run the following command:
|
||||
|
||||
```sh
|
||||
sudo apt-get install git openjdk-11-jdk maven unzip
|
||||
sudo apt-get install git openjdk-11-jdk maven
|
||||
```
|
||||
|
||||
### Getting the Code
|
||||
@@ -49,19 +49,6 @@ Java interfaces for the current driver run the following commands:
|
||||
./scripts/generate_api.sh
|
||||
```
|
||||
|
||||
#### Updating driver version
|
||||
|
||||
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
|
||||
|
||||
```bash
|
||||
cat > scripts/CLI_VERSION
|
||||
<paste new version>
|
||||
^D
|
||||
./scripts/download_driver_for_all_platforms.sh -f
|
||||
./scripts/generate_api.sh
|
||||
./scripts/update_readme.sh
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- We try to follow [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)
|
||||
|
||||
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->107.0.5304.18<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->105.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->101.0.4929.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->97.0.1<!-- 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,18 +43,10 @@ To run Playwright simply add following dependency to your Maven project:
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.26.0</version>
|
||||
<version>1.17.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.26.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.
|
||||
@@ -181,7 +173,7 @@ public class InterceptNetworkRequests {
|
||||
|
||||
## Documentation
|
||||
|
||||
Check out our official [documentation site](https://playwright.dev/java).
|
||||
Check out our [new documentation site](https://playwright.dev/java)!.
|
||||
|
||||
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.27.1</version>
|
||||
<version>1.20.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
|
||||
+9
-48
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl.driver.jar;
|
||||
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -28,10 +26,7 @@ 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
|
||||
@@ -42,29 +37,11 @@ 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(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());
|
||||
}
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
extractDriverToTempDir();
|
||||
logMessage("extracted driver from jar to " + driverPath());
|
||||
if (installBrowsers)
|
||||
installBrowsers(env);
|
||||
}
|
||||
@@ -78,16 +55,13 @@ 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 = createProcessBuilder();
|
||||
pb.command().add("install");
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
|
||||
pb.environment().putAll(env);
|
||||
setRequiredEnvironmentVariables(pb);
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -106,10 +80,9 @@ public class DriverJar extends Driver {
|
||||
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
|
||||
}
|
||||
|
||||
void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
private 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.
|
||||
@@ -121,12 +94,6 @@ 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 {
|
||||
@@ -170,17 +137,11 @@ public class DriverJar extends Driver {
|
||||
|
||||
private static String platformDir() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch").toLowerCase();
|
||||
|
||||
if (name.contains("windows")) {
|
||||
return "win32_x64";
|
||||
}
|
||||
if (name.contains("linux")) {
|
||||
if (arch.equals("aarch64")) {
|
||||
return "linux-arm64";
|
||||
} else {
|
||||
return "linux";
|
||||
}
|
||||
return "linux";
|
||||
}
|
||||
if (name.contains("mac os x")) {
|
||||
return "mac";
|
||||
@@ -189,7 +150,7 @@ public class DriverJar extends Driver {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path driverDir() {
|
||||
Path driverDir() {
|
||||
return driverTempDir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import com.microsoft.playwright.impl.DriverJar;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestInstall {
|
||||
@BeforeEach
|
||||
void clearSystemProperties() {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
System.clearProperty("playwright.driver.tmpdir");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightCliInstalled() throws Exception {
|
||||
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
assertTrue(Files.exists(cli));
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
boolean result = p.waitFor(1, TimeUnit.MINUTES);
|
||||
assertTrue(result, "Timed out waiting for browsers to install");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
|
||||
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
|
||||
DriverJar driver = new DriverJar();
|
||||
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
|
||||
}
|
||||
}
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.driver.jar;
|
||||
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
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 {
|
||||
private static boolean isPortAvailable(int port) {
|
||||
try (ServerSocket ignored = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int unusedPort() {
|
||||
for (int i = 10000; i < 11000; i++) {
|
||||
if (isPortAvailable(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Cannot find unused local port");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearSystemProperties() {
|
||||
// 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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) throws NoSuchFieldException, IllegalAccessException {
|
||||
Map<String,String> env = new HashMap<>();
|
||||
|
||||
// On macOS we can only use 127.0.0.1, so pick unused port instead.
|
||||
// https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x
|
||||
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.1:" + unusedPort());
|
||||
// Make sure the browsers are not installed yet by pointing at an empty dir.
|
||||
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
|
||||
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
|
||||
|
||||
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 {
|
||||
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
|
||||
assertTrue(Files.exists(driver.driverPath()));
|
||||
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("install");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
boolean result = p.waitFor(1, TimeUnit.MINUTES);
|
||||
assertTrue(result, "Timed out waiting for browsers to install");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
|
||||
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
|
||||
DriverJar driver = new DriverJar();
|
||||
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverDefaultImpl() {
|
||||
assertDoesNotThrow(() -> Driver.createAndInstall(Collections.emptyMap(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
|
||||
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
|
||||
RuntimeException thrown =
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> Driver.createAndInstall(Collections.emptyMap(), false));
|
||||
assertEquals("Failed to create driver", thrown.getMessage());
|
||||
}
|
||||
|
||||
@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
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.27.1</version>
|
||||
<version>1.20.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
+16
-43
@@ -14,56 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl.driver;
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
|
||||
|
||||
/**
|
||||
* This class provides access to playwright-cli. It can be either preinstalled
|
||||
* in the host system and its path is passed as a system property or it can be
|
||||
* 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 {
|
||||
private final Path driverDir;
|
||||
PreinstalledDriver(Path driverDir) {
|
||||
logMessage("created PreinstalledDriver: " + driverDir);
|
||||
this.driverDir = driverDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Boolean installBrowsers) {
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path driverDir() {
|
||||
Path driverDir() {
|
||||
return driverDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
if (instance == null) {
|
||||
instance = createAndInstall(env, installBrowsers);
|
||||
try {
|
||||
instance = createDriver();
|
||||
instance.initialize(env, installBrowsers);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
return instance.driverPath();
|
||||
}
|
||||
|
||||
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;
|
||||
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
|
||||
|
||||
public Path driverPath() {
|
||||
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
@@ -71,16 +65,13 @@ public abstract class Driver {
|
||||
return driverDir().resolve(cliFileName);
|
||||
}
|
||||
|
||||
public ProcessBuilder createProcessBuilder() {
|
||||
ProcessBuilder pb = new ProcessBuilder(driverPath().toString());
|
||||
pb.environment().putAll(env);
|
||||
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
|
||||
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() {
|
||||
@@ -94,34 +85,16 @@ 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 newInstance() throws Exception {
|
||||
private static Driver createDriver() 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.driver.jar.DriverJar");
|
||||
Class<?> jarDriver = Class.forName(driverImpl);
|
||||
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
|
||||
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
protected abstract Path driverDir();
|
||||
|
||||
protected static void logMessage(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:install " + message);
|
||||
}
|
||||
abstract Path driverDir();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.driver;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
class DriverLogging {
|
||||
private static final boolean isEnabled;
|
||||
static {
|
||||
String debug = System.getenv("DEBUG");
|
||||
isEnabled = (debug != null) && debug.contains("pw:install");
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
|
||||
|
||||
static void logWithTimestamp(String message) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
// This matches log format produced by the server.
|
||||
String timestamp = ZonedDateTime.now().format(timestampFormat);
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.27.1</version>
|
||||
<version>1.20.1</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -15,7 +15,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.22.0</version>
|
||||
<version>1.17.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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 org.example;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class SelectorsAndKeyboardManipulation {
|
||||
public static void main(String[] args) {
|
||||
try(Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://playwright.dev/java/");
|
||||
page.locator("text=SearchK").click();
|
||||
page.locator("[placeholder=\"Search docs\"]").fill("getting started");
|
||||
page.locator("div[role=\"button\"]:has-text(\"CancelIntroductionGetting startedInstallationGetting startedUsageGetting start\")").click();
|
||||
page.waitForSelector("h1:has-text(\"Getting started\")"); // Waits for the new page to load before screenshotting.
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("Screenshot.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.27.1</version>
|
||||
<version>1.20.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
|
||||
@@ -30,15 +30,15 @@ import java.nio.file.Path;
|
||||
*
|
||||
* <p> **Cookie management**
|
||||
*
|
||||
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
|
||||
* <p> {@code APIRequestContext} retuned 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 should 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 shoud 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,31 +86,6 @@ 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) {
|
||||
@@ -120,31 +95,6 @@ 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.
|
||||
*/
|
||||
@@ -153,31 +103,6 @@ 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) {
|
||||
@@ -187,31 +112,6 @@ 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.
|
||||
*/
|
||||
@@ -221,13 +121,6 @@ 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) {
|
||||
@@ -238,13 +131,6 @@ 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.
|
||||
*/
|
||||
@@ -292,39 +178,6 @@ 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) {
|
||||
@@ -335,39 +188,6 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A Browser is created via {@link BrowserType#launch BrowserType.launch()}. An example of using a {@code Browser} to create a
|
||||
@@ -95,6 +94,9 @@ 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.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
@@ -141,17 +143,6 @@ public interface Browser extends AutoCloseable {
|
||||
* 'http://per-context' } })}.
|
||||
*/
|
||||
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 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;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -162,7 +153,6 @@ public interface Browser extends AutoCloseable {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -184,15 +174,6 @@ public interface Browser extends AutoCloseable {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
@@ -205,7 +186,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -281,6 +262,9 @@ 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.
|
||||
*/
|
||||
public NewContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
@@ -379,23 +363,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* 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) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public NewContextOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -412,14 +379,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -468,18 +427,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
@@ -498,7 +445,7 @@ public interface Browser extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -575,6 +522,9 @@ 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.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
@@ -621,17 +571,6 @@ public interface Browser extends AutoCloseable {
|
||||
* 'http://per-context' } })}.
|
||||
*/
|
||||
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 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;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -642,7 +581,6 @@ public interface Browser extends AutoCloseable {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -664,15 +602,6 @@ public interface Browser extends AutoCloseable {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
@@ -685,7 +614,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -761,6 +690,9 @@ 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.
|
||||
*/
|
||||
public NewPageOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
@@ -859,23 +791,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* 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) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public NewPageOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -892,14 +807,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -948,18 +855,6 @@ public interface Browser extends AutoCloseable {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewPageOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
@@ -978,7 +873,7 @@ public interface Browser extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -1052,10 +947,6 @@ public interface Browser extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the browser type (chromium, firefox or webkit) that the browser belongs to.
|
||||
*/
|
||||
BrowserType browserType();
|
||||
/**
|
||||
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
|
||||
* its pages (if any were opened).
|
||||
@@ -1063,10 +954,6 @@ public interface Browser extends AutoCloseable {
|
||||
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
|
||||
* browser server.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link BrowserContext#close
|
||||
* BrowserContext.close()} on any {@code BrowserContext}'s you explicitly created earlier with {@link Browser#newContext
|
||||
* Browser.newContext()} **before** calling {@link Browser#close Browser.close()}.
|
||||
*
|
||||
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
|
||||
*/
|
||||
void close();
|
||||
@@ -1086,11 +973,6 @@ public interface Browser extends AutoCloseable {
|
||||
boolean isConnected();
|
||||
/**
|
||||
* 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 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.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
|
||||
* // Create a new incognito browser context.
|
||||
@@ -1098,10 +980,6 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Graceful close up everything
|
||||
* context.close();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*/
|
||||
default BrowserContext newContext() {
|
||||
@@ -1109,11 +987,6 @@ 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 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.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
|
||||
* // Create a new incognito browser context.
|
||||
@@ -1121,10 +994,6 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Graceful close up everything
|
||||
* context.close();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*/
|
||||
BrowserContext newContext(NewContextOptions options);
|
||||
@@ -1148,9 +1017,8 @@ public interface Browser extends AutoCloseable {
|
||||
Page newPage(NewPageOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1168,9 +1036,8 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1186,9 +1053,8 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1204,9 +1070,8 @@ public interface Browser extends AutoCloseable {
|
||||
void startTracing(Page page, StartTracingOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> Returns the buffer with trace data.
|
||||
*/
|
||||
|
||||
@@ -67,7 +67,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* done and its response has started loading in the popup.
|
||||
* <pre>{@code
|
||||
* Page newPage = context.waitForPage(() -> {
|
||||
* page.locator("a[target=_blank]").click();
|
||||
* page.click("a[target=_blank]");
|
||||
* });
|
||||
* System.out.println(newPage.evaluate("location.href"));
|
||||
* }</pre>
|
||||
@@ -174,62 +174,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class RouteFromHAROptions {
|
||||
/**
|
||||
* <ul>
|
||||
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
|
||||
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to abort.
|
||||
*/
|
||||
public HarNotFound notFound;
|
||||
/**
|
||||
* If specified, updates the given HAR with the actual network information instead of serving from file.
|
||||
*/
|
||||
public Boolean update;
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public Object url;
|
||||
|
||||
/**
|
||||
* <ul>
|
||||
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
|
||||
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to abort.
|
||||
*/
|
||||
public RouteFromHAROptions setNotFound(HarNotFound notFound) {
|
||||
this.notFound = notFound;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, updates the given HAR with the actual network information instead of serving from file.
|
||||
*/
|
||||
public RouteFromHAROptions setUpdate(boolean update) {
|
||||
this.update = update;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public RouteFromHAROptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public RouteFromHAROptions setUrl(Pattern url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StorageStateOptions {
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
@@ -404,7 +348,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.getByRole("button").click();
|
||||
* page.click("button");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -463,7 +407,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.getByRole("button").click();
|
||||
* page.click("button");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -533,7 +477,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>\n");
|
||||
* page.getByRole("button").click();
|
||||
* page.click("button");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -609,9 +553,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -661,9 +605,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -711,9 +655,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -763,9 +707,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -813,9 +757,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -865,9 +809,9 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
|
||||
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
@@ -911,32 +855,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* @param handler handler function to route the request.
|
||||
*/
|
||||
void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
|
||||
/**
|
||||
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
|
||||
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
|
||||
*
|
||||
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
|
||||
* is a relative path, then it is resolved relative to the current working directory.
|
||||
*/
|
||||
default void routeFromHAR(Path har) {
|
||||
routeFromHAR(har, null);
|
||||
}
|
||||
/**
|
||||
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
|
||||
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
|
||||
*
|
||||
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
|
||||
* is a relative path, then it is resolved relative to the current working directory.
|
||||
*/
|
||||
void routeFromHAR(Path har, RouteFromHAROptions options);
|
||||
/**
|
||||
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
|
||||
* <ul>
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.microsoft.playwright;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
|
||||
@@ -53,7 +52,8 @@ public interface BrowserType {
|
||||
*/
|
||||
public Double slowMo;
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
@@ -73,7 +73,8 @@ public interface BrowserType {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
*/
|
||||
public ConnectOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
@@ -442,6 +443,9 @@ 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.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
@@ -513,17 +517,6 @@ public interface BrowserType {
|
||||
* Network proxy settings.
|
||||
*/
|
||||
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 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;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -534,7 +527,6 @@ public interface BrowserType {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -556,21 +548,12 @@ public interface BrowserType {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
*/
|
||||
public Double slowMo;
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -722,6 +705,9 @@ 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.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
@@ -859,23 +845,6 @@ public interface BrowserType {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* 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) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -892,14 +861,6 @@ public interface BrowserType {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
|
||||
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
|
||||
@@ -948,18 +909,6 @@ public interface BrowserType {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public LaunchPersistentContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
*/
|
||||
@@ -968,7 +917,7 @@ public interface BrowserType {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
@@ -1022,9 +971,7 @@ public interface BrowserType {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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).
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
*
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
*/
|
||||
@@ -1032,24 +979,17 @@ public interface BrowserType {
|
||||
return connect(wsEndpoint, null);
|
||||
}
|
||||
/**
|
||||
* 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).
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
*
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
*/
|
||||
Browser connect(String wsEndpoint, ConnectOptions options);
|
||||
/**
|
||||
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
*
|
||||
* <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}.
|
||||
@@ -1058,16 +998,11 @@ public interface BrowserType {
|
||||
return connectOverCDP(endpointURL, null);
|
||||
}
|
||||
/**
|
||||
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
*
|
||||
* <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.Driver;
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -29,9 +29,10 @@ import static java.util.Arrays.asList;
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
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);
|
||||
|
||||
@@ -19,28 +19,7 @@ package com.microsoft.playwright;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event. For
|
||||
* each console messages logged in the page there will be corresponding event in the Playwright context.
|
||||
* <pre>{@code
|
||||
* // Listen for all System.out.printlns
|
||||
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
|
||||
*
|
||||
* // Listen for all console events and handle errors
|
||||
* page.onConsoleMessage(msg -> {
|
||||
* if ("error".equals(msg.type()))
|
||||
* System.out.println("Error text: " + msg.text());
|
||||
* });
|
||||
*
|
||||
* // Get the next System.out.println
|
||||
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
|
||||
* // Issue console.log inside the page
|
||||
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
|
||||
* });
|
||||
*
|
||||
* // Deconstruct console.log arguments
|
||||
* msg.args().get(0).jsonValue() // hello
|
||||
* msg.args().get(1).jsonValue() // 42
|
||||
* }</pre>
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event.
|
||||
*/
|
||||
public interface ConsoleMessage {
|
||||
/**
|
||||
|
||||
@@ -27,8 +27,14 @@ 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.click("a"));
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> {
|
||||
* page.getByText("Download file").click();
|
||||
* page.click("a");
|
||||
* });
|
||||
* // 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.getByText("Submit");
|
||||
* Locator locator = page.locator("text=Submit");
|
||||
* locator.hover();
|
||||
* locator.click();
|
||||
* }</pre>
|
||||
@@ -576,17 +576,10 @@ public interface ElementHandle extends JSHandle {
|
||||
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
|
||||
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to {@code "allow"} that leaves animations untouched.
|
||||
*/
|
||||
public ScreenshotAnimations animations;
|
||||
/**
|
||||
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
|
||||
* Defaults to {@code "hide"}.
|
||||
*/
|
||||
public ScreenshotCaret caret;
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
|
||||
* {@code #FF00FF} that completely covers its bounding box.
|
||||
*/
|
||||
public List<Locator> mask;
|
||||
@@ -605,14 +598,6 @@ public interface ElementHandle extends JSHandle {
|
||||
* The quality of the image, between 0-100. Not applicable to {@code png} images.
|
||||
*/
|
||||
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
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotScale scale;
|
||||
/**
|
||||
* 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
|
||||
@@ -631,23 +616,13 @@ public interface ElementHandle extends JSHandle {
|
||||
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
|
||||
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to {@code "allow"} that leaves animations untouched.
|
||||
*/
|
||||
public ScreenshotOptions setAnimations(ScreenshotAnimations animations) {
|
||||
this.animations = animations;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
|
||||
* Defaults to {@code "hide"}.
|
||||
*/
|
||||
public ScreenshotOptions setCaret(ScreenshotCaret caret) {
|
||||
this.caret = caret;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
|
||||
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
|
||||
* {@code #FF00FF} that completely covers its bounding box.
|
||||
*/
|
||||
public ScreenshotOptions setMask(List<Locator> mask) {
|
||||
@@ -678,17 +653,6 @@ public interface ElementHandle extends JSHandle {
|
||||
this.quality = quality;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
* high-dpi devices will be twice as large or even larger.
|
||||
*
|
||||
* <p> Defaults to {@code "device"}.
|
||||
*/
|
||||
public ScreenshotOptions setScale(ScreenshotScale scale) {
|
||||
this.scale = scale;
|
||||
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
|
||||
@@ -1166,7 +1130,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 than one
|
||||
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
|
||||
* element, the call throws an exception.
|
||||
*/
|
||||
public Boolean strict;
|
||||
@@ -1193,7 +1157,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 than one
|
||||
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
|
||||
* element, the call throws an exception.
|
||||
*/
|
||||
public WaitForSelectorOptions setStrict(boolean strict) {
|
||||
@@ -1214,7 +1178,7 @@ public interface ElementHandle extends JSHandle {
|
||||
* 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.
|
||||
*
|
||||
* <p> Scrolling affects the returned bounding box, similarly to <a
|
||||
* <p> Scrolling affects the returned bonding box, similarly to <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">Element.getBoundingClientRect</a>.
|
||||
* That means {@code x} and/or {@code y} may be negative.
|
||||
*
|
||||
@@ -1438,8 +1402,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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
default Object evalOnSelector(String selector, String expression) {
|
||||
return evalOnSelector(selector, expression, null);
|
||||
@@ -1464,8 +1428,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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
Object evalOnSelector(String selector, String expression, Object arg);
|
||||
@@ -1489,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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
default Object evalOnSelectorAll(String selector, String expression) {
|
||||
return evalOnSelectorAll(selector, expression, null);
|
||||
@@ -1515,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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
Object evalOnSelectorAll(String selector, String expression, Object arg);
|
||||
@@ -1605,21 +1569,13 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
String innerText();
|
||||
/**
|
||||
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
|
||||
*
|
||||
* <p> Throws for non-input elements. 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>, returns the value of the
|
||||
* control.
|
||||
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
|
||||
*/
|
||||
default String inputValue() {
|
||||
return inputValue(null);
|
||||
}
|
||||
/**
|
||||
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
|
||||
*
|
||||
* <p> Throws for non-input elements. 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>, returns the value of the
|
||||
* control.
|
||||
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
|
||||
*/
|
||||
String inputValue(InputValueOptions options);
|
||||
/**
|
||||
@@ -1721,27 +1677,19 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
List<ElementHandle> querySelectorAll(String selector);
|
||||
/**
|
||||
* This method captures a screenshot of the page, clipped to the size and position of this particular element. If the
|
||||
* element is covered by other elements, it will not be actually visible on the screenshot. If the element is a scrollable
|
||||
* container, only the currently scrolled content will be visible on the screenshot.
|
||||
* Returns the buffer with the captured screenshot.
|
||||
*
|
||||
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
|
||||
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
|
||||
*
|
||||
* <p> Returns the buffer with the captured screenshot.
|
||||
*/
|
||||
default byte[] screenshot() {
|
||||
return screenshot(null);
|
||||
}
|
||||
/**
|
||||
* This method captures a screenshot of the page, clipped to the size and position of this particular element. If the
|
||||
* element is covered by other elements, it will not be actually visible on the screenshot. If the element is a scrollable
|
||||
* container, only the currently scrolled content will be visible on the screenshot.
|
||||
* Returns the buffer with the captured screenshot.
|
||||
*
|
||||
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
|
||||
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
|
||||
*
|
||||
* <p> Returns the buffer with the captured screenshot.
|
||||
*/
|
||||
byte[] screenshot(ScreenshotOptions options);
|
||||
/**
|
||||
@@ -2091,10 +2039,6 @@ public interface ElementHandle extends JSHandle {
|
||||
/**
|
||||
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
|
||||
* the element and selects all its text content.
|
||||
*
|
||||
* <p> 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>, focuses and selects text
|
||||
* in the control instead.
|
||||
*/
|
||||
default void selectText() {
|
||||
selectText(null);
|
||||
@@ -2102,10 +2046,6 @@ public interface ElementHandle extends JSHandle {
|
||||
/**
|
||||
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
|
||||
* the element and selects all its text content.
|
||||
*
|
||||
* <p> 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>, focuses and selects text
|
||||
* in the control instead.
|
||||
*/
|
||||
void selectText(SelectTextOptions options);
|
||||
/**
|
||||
@@ -2149,99 +2089,75 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
void setChecked(boolean checked, SetCheckedOptions options);
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setInputFiles(Path files) {
|
||||
setInputFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setInputFiles(Path files, SetInputFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setInputFiles(Path[] files) {
|
||||
setInputFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setInputFiles(Path[] files, SetInputFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setInputFiles(FilePayload files) {
|
||||
setInputFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setInputFiles(FilePayload files, SetInputFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setInputFiles(FilePayload[] files) {
|
||||
setInputFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* This method expects {@code elementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
|
||||
*
|
||||
* <p> This method expects {@code ElementHandle} to point to an <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. 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>, targets the control
|
||||
* instead.
|
||||
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
|
||||
* are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
|
||||
/**
|
||||
|
||||
@@ -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.getByText("Upload").click());
|
||||
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
|
||||
* fileChooser.setFiles(Paths.get("myfile.pdf"));
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -74,50 +74,50 @@ public interface FileChooser {
|
||||
Page page();
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path[] files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload[] files, SetFilesOptions options);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@@ -31,13 +30,13 @@ import java.util.regex.Pattern;
|
||||
* <p> **Strictness**
|
||||
*
|
||||
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
|
||||
* a given selector.
|
||||
* given selector.
|
||||
* <pre>{@code
|
||||
* // Throws if there are several frames in DOM:
|
||||
* page.frame_locator(".result-frame").getByRole("button").click();
|
||||
* page.frame_locator(".result-frame").locator("button").click();
|
||||
*
|
||||
* // Works because we explicitly tell locator to pick the first frame:
|
||||
* page.frame_locator(".result-frame").first().getByRole("button").click();
|
||||
* page.frame_locator(".result-frame").first().locator("button").click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> **Converting Locator to FrameLocator**
|
||||
@@ -49,226 +48,6 @@ 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.
|
||||
*/
|
||||
public Boolean exact;
|
||||
|
||||
/**
|
||||
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
|
||||
* expression.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public Boolean exact;
|
||||
|
||||
/**
|
||||
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
|
||||
* expression.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public Boolean exact;
|
||||
|
||||
/**
|
||||
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
|
||||
* expression.
|
||||
*/
|
||||
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. Available values for
|
||||
* checked are {@code true}, {@code false} and {@code "mixed"}.
|
||||
*
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
|
||||
*/
|
||||
public Boolean checked;
|
||||
/**
|
||||
* A boolean 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;
|
||||
/**
|
||||
* A boolean 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;
|
||||
/**
|
||||
* A boolean attribute 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;
|
||||
/**
|
||||
* A string attribute that matches <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
|
||||
*
|
||||
* <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}. Available values for pressed are {@code true}, {@code false} and {@code "mixed"}.
|
||||
*
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-pressed">{@code aria-pressed}</a>.
|
||||
*/
|
||||
public Boolean pressed;
|
||||
/**
|
||||
* A boolean 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. Available values for
|
||||
* checked are {@code true}, {@code false} and {@code "mixed"}.
|
||||
*
|
||||
* <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;
|
||||
}
|
||||
/**
|
||||
* A boolean 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;
|
||||
}
|
||||
/**
|
||||
* A boolean 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;
|
||||
}
|
||||
/**
|
||||
* A boolean attribute 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;
|
||||
}
|
||||
/**
|
||||
* A string attribute that matches <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
|
||||
*
|
||||
* <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;
|
||||
}
|
||||
/**
|
||||
* A string attribute that matches <a href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
|
||||
*
|
||||
* <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}. Available values for pressed are {@code true}, {@code false} and {@code "mixed"}.
|
||||
*
|
||||
* <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;
|
||||
}
|
||||
/**
|
||||
* A boolean 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.
|
||||
*/
|
||||
public Boolean exact;
|
||||
|
||||
/**
|
||||
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
|
||||
* expression.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public Boolean exact;
|
||||
|
||||
/**
|
||||
* Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular
|
||||
* expression.
|
||||
*/
|
||||
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.
|
||||
@@ -278,9 +57,8 @@ public interface FrameLocator {
|
||||
*/
|
||||
public Locator has;
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public Object hasText;
|
||||
|
||||
@@ -295,18 +73,16 @@ public interface FrameLocator {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(String hasText) {
|
||||
this.hasText = hasText;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(Pattern hasText) {
|
||||
this.hasText = hasText;
|
||||
@@ -325,202 +101,12 @@ 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.
|
||||
*
|
||||
* @param text Text to locate the element for.
|
||||
*/
|
||||
default Locator getByText(String text) {
|
||||
return getByText(text, null);
|
||||
}
|
||||
/**
|
||||
* Allows locating elements that contain given text.
|
||||
*
|
||||
* @param text Text to locate the element for.
|
||||
*/
|
||||
Locator getByText(String text, GetByTextOptions options);
|
||||
/**
|
||||
* Allows locating elements that contain given text.
|
||||
*
|
||||
* @param text Text to locate the element for.
|
||||
*/
|
||||
default Locator getByText(Pattern text) {
|
||||
return getByText(text, null);
|
||||
}
|
||||
/**
|
||||
* Allows locating elements that contain given text.
|
||||
*
|
||||
* @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 "Submit":
|
||||
*
|
||||
* @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 "Submit":
|
||||
*
|
||||
* @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 "Submit":
|
||||
*
|
||||
* @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 "Submit":
|
||||
*
|
||||
* @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 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>.
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
*
|
||||
* @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.
|
||||
@@ -529,17 +115,14 @@ public interface FrameLocator {
|
||||
return locator(selector, null);
|
||||
}
|
||||
/**
|
||||
* 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>.
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
Locator locator(String selector, LocatorOptions options);
|
||||
/**
|
||||
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
|
||||
* Returns locator to the n-th matching frame.
|
||||
*/
|
||||
FrameLocator nth(int index);
|
||||
}
|
||||
|
||||
@@ -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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
JSHandle evaluateHandle(String expression, Object arg);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,12 +122,12 @@ public interface Mouse {
|
||||
}
|
||||
class MoveOptions {
|
||||
/**
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public Integer steps;
|
||||
|
||||
/**
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public MoveOptions setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,9 +58,8 @@ public interface Request {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* An object with the request HTTP headers. The header names are lower-cased. Note that this method does not return
|
||||
* security-related headers, including cookie-related ones. You can use {@link Request#allHeaders Request.allHeaders()} for
|
||||
* complete list of headers that include {@code cookie} information.
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
|
||||
* Request.allHeaders()} instead.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
|
||||
@@ -40,14 +40,8 @@ public interface Response {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* An object with the response HTTP headers. The header names are lower-cased. Note that this method does not return
|
||||
* security-related headers, including cookie-related ones. You can use {@link Response#allHeaders Response.allHeaders()}
|
||||
* for complete list of headers that include {@code cookie} information.
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
|
||||
* Response.allHeaders()} instead.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
|
||||
@@ -22,8 +22,6 @@ import java.util.*;
|
||||
/**
|
||||
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
|
||||
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
|
||||
*
|
||||
* <p> Learn more about <a href="https://playwright.dev/java/docs/network">networking</a>.
|
||||
*/
|
||||
public interface Route {
|
||||
class ResumeOptions {
|
||||
@@ -80,62 +78,6 @@ public interface Route {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FallbackOptions {
|
||||
/**
|
||||
* If set changes the request HTTP headers. Header values will be converted to a string.
|
||||
*/
|
||||
public Map<String, String> headers;
|
||||
/**
|
||||
* If set changes the request method (e.g. GET or POST)
|
||||
*/
|
||||
public String method;
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public Object postData;
|
||||
/**
|
||||
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
|
||||
* matching, all the routes are matched using the original request URL.
|
||||
*/
|
||||
public String url;
|
||||
|
||||
/**
|
||||
* If set changes the request HTTP headers. Header values will be converted to a string.
|
||||
*/
|
||||
public FallbackOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request method (e.g. GET or POST)
|
||||
*/
|
||||
public FallbackOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public FallbackOptions setPostData(String postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public FallbackOptions setPostData(byte[] postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
|
||||
* matching, all the routes are matched using the original request URL.
|
||||
*/
|
||||
public FallbackOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FulfillOptions {
|
||||
/**
|
||||
* Optional response body as text.
|
||||
@@ -256,8 +198,8 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
@@ -271,133 +213,13 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
void resume(ResumeOptions options);
|
||||
/**
|
||||
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
|
||||
* registered route can always override all the previous ones. In the example below, request will be handled by the
|
||||
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
|
||||
* registered route.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs last.
|
||||
* route.abort();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs second.
|
||||
* route.fallback();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs first.
|
||||
* route.fallback();
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
|
||||
* API calls vs page resources or GET requests vs POST requests as in the example below.
|
||||
* <pre>{@code
|
||||
* // Handle GET requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("GET")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling GET only.
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* // Handle POST requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("POST")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling POST only.
|
||||
* // ...
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
|
||||
* url, method, headers and postData of the request.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
default void fallback() {
|
||||
fallback(null);
|
||||
}
|
||||
/**
|
||||
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
|
||||
* registered route can always override all the previous ones. In the example below, request will be handled by the
|
||||
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
|
||||
* registered route.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs last.
|
||||
* route.abort();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs second.
|
||||
* route.fallback();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs first.
|
||||
* route.fallback();
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
|
||||
* API calls vs page resources or GET requests vs POST requests as in the example below.
|
||||
* <pre>{@code
|
||||
* // Handle GET requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("GET")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling GET only.
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* // Handle POST requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("POST")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling POST only.
|
||||
* // ...
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
|
||||
* url, method, headers and postData of the request.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
void fallback(FallbackOptions options);
|
||||
/**
|
||||
* Fulfills route's request with given response.
|
||||
*
|
||||
|
||||
@@ -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. The script is evaluated in the page context.
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -63,7 +63,7 @@ public interface Selectors {
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
@@ -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. The script is evaluated in the page context.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
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. The script is evaluated in the page context.
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -98,7 +98,7 @@ public interface Selectors {
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
@@ -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. The script is evaluated in the page context.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
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. The script is evaluated in the page context.
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -131,7 +131,7 @@ public interface Selectors {
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
@@ -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. The script is evaluated in the page context.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
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. The script is evaluated in the page context.
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -166,7 +166,7 @@ public interface Selectors {
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
@@ -174,15 +174,8 @@ 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. The script is evaluated in the page context.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ public interface Tracing {
|
||||
public Boolean snapshots;
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
public Boolean sources;
|
||||
/**
|
||||
@@ -93,8 +92,7 @@ public interface Tracing {
|
||||
}
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
public StartOptions setSources(boolean sources) {
|
||||
this.sources = sources;
|
||||
@@ -192,7 +190,7 @@ public interface Tracing {
|
||||
* page.navigate("https://playwright.dev");
|
||||
*
|
||||
* context.tracing().startChunk();
|
||||
* page.getByText("Get Started").click();
|
||||
* page.click("text=Get Started");
|
||||
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
||||
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||
* .setPath(Paths.get("trace1.zip")));
|
||||
@@ -219,7 +217,7 @@ public interface Tracing {
|
||||
* page.navigate("https://playwright.dev");
|
||||
*
|
||||
* context.tracing().startChunk();
|
||||
* page.getByText("Get Started").click();
|
||||
* page.click("text=Get Started");
|
||||
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
||||
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||
* .setPath(Paths.get("trace1.zip")));
|
||||
|
||||
@@ -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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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.
|
||||
*/
|
||||
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 the expresion evaluates to a function, the function is
|
||||
* automatically invoked.
|
||||
* @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 arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
JSHandle evaluateHandle(String expression, Object arg);
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public interface APIResponseAssertions {
|
||||
*/
|
||||
APIResponseAssertions not();
|
||||
/**
|
||||
* Ensures the response status code is within {@code 200..299} range.
|
||||
* Ensures the response status code is within [200..299] range.
|
||||
* <pre>{@code
|
||||
* assertThat(response).isOK();
|
||||
* }</pre>
|
||||
|
||||
+67
-516
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
|
||||
* @Test
|
||||
* void statusBecomesSubmitted() {
|
||||
* ...
|
||||
* page.getByRole("button").click();
|
||||
* page.click("#submit-button");
|
||||
* assertThat(page.locator(".status")).hasText("Submitted");
|
||||
* }
|
||||
* }
|
||||
@@ -72,16 +72,11 @@ 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.
|
||||
*/
|
||||
@@ -105,16 +100,11 @@ 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.
|
||||
*/
|
||||
@@ -156,7 +146,6 @@ public interface LocatorAssertions {
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public Double timeout;
|
||||
public Boolean visible;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
@@ -165,17 +154,8 @@ public interface LocatorAssertions {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
public IsVisibleOptions setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class ContainsTextOptions {
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public Boolean ignoreCase;
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
@@ -185,14 +165,6 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
public Boolean useInnerText;
|
||||
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public ContainsTextOptions setIgnoreCase(boolean ignoreCase) {
|
||||
this.ignoreCase = ignoreCase;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
@@ -293,11 +265,6 @@ public interface LocatorAssertions {
|
||||
}
|
||||
}
|
||||
class HasTextOptions {
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public Boolean ignoreCase;
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
@@ -307,14 +274,6 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
public Boolean useInnerText;
|
||||
|
||||
/**
|
||||
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
public HasTextOptions setIgnoreCase(boolean ignoreCase) {
|
||||
this.ignoreCase = ignoreCase;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
@@ -344,20 +303,6 @@ public interface LocatorAssertions {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class HasValuesOptions {
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public HasValuesOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain text
|
||||
* {@code "error"}:
|
||||
@@ -369,7 +314,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a checked input.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByLabel("Subscribe to newsletter")).isChecked();
|
||||
* assertThat(page.locator(".subscribe")).isChecked();
|
||||
* }</pre>
|
||||
*/
|
||||
default void isChecked() {
|
||||
@@ -378,16 +323,12 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a checked input.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByLabel("Subscribe to newsletter")).isChecked();
|
||||
* assertThat(page.locator(".subscribe")).isChecked();
|
||||
* }</pre>
|
||||
*/
|
||||
void isChecked(IsCheckedOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
|
||||
* via <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
|
||||
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
|
||||
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
|
||||
* Ensures the {@code Locator} points to a disabled element.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("button.submit")).isDisabled();
|
||||
* }</pre>
|
||||
@@ -396,11 +337,7 @@ public interface LocatorAssertions {
|
||||
isDisabled(null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
|
||||
* via <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
|
||||
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
|
||||
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
|
||||
* Ensures the {@code Locator} points to a disabled element.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("button.submit")).isDisabled();
|
||||
* }</pre>
|
||||
@@ -409,7 +346,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an editable element.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).isEditable();
|
||||
* assertThat(page.locator("input")).isEditable();
|
||||
* }</pre>
|
||||
*/
|
||||
default void isEditable() {
|
||||
@@ -418,7 +355,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an editable element.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).isEditable();
|
||||
* assertThat(page.locator("input")).isEditable();
|
||||
* }</pre>
|
||||
*/
|
||||
void isEditable(IsEditableOptions options);
|
||||
@@ -457,7 +394,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a focused DOM node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).isFocused();
|
||||
* assertThat(page.locator("input")).isFocused();
|
||||
* }</pre>
|
||||
*/
|
||||
default void isFocused() {
|
||||
@@ -466,13 +403,13 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a focused DOM node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).isFocused();
|
||||
* assertThat(page.locator("input")).isFocused();
|
||||
* }</pre>
|
||||
*/
|
||||
void isFocused(IsFocusedOptions options);
|
||||
/**
|
||||
* 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.
|
||||
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
|
||||
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isHidden();
|
||||
* }</pre>
|
||||
@@ -481,28 +418,26 @@ public interface LocatorAssertions {
|
||||
isHidden(null);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
|
||||
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isHidden();
|
||||
* }</pre>
|
||||
*/
|
||||
void isHidden(IsHiddenOptions options);
|
||||
/**
|
||||
* 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.
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isVisible();
|
||||
* assertThat(page.locator(".my-element")).toBeVisible();
|
||||
* }</pre>
|
||||
*/
|
||||
default void isVisible() {
|
||||
isVisible(null);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isVisible();
|
||||
* assertThat(page.locator(".my-element")).toBeVisible();
|
||||
* }</pre>
|
||||
*/
|
||||
void isVisible(IsVisibleOptions options);
|
||||
@@ -513,29 +448,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -550,29 +465,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -585,29 +480,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -622,29 +497,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -657,29 +512,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -694,29 +529,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -729,29 +544,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -766,29 +561,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).containsText("substring");
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .list-item")).containsText(new String[] {"Text 1", "Text 4", "Text 5"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -804,7 +579,7 @@ public interface LocatorAssertions {
|
||||
* @param value Expected attribute value.
|
||||
*/
|
||||
default void hasAttribute(String name, String value) {
|
||||
hasAttribute(name, value, (HasAttributeOptions) null);
|
||||
hasAttribute(name, value, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given attribute.
|
||||
@@ -826,7 +601,7 @@ public interface LocatorAssertions {
|
||||
* @param value Expected attribute value.
|
||||
*/
|
||||
default void hasAttribute(String name, Pattern value) {
|
||||
hasAttribute(name, value, (HasAttributeOptions) null);
|
||||
hasAttribute(name, value, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given attribute.
|
||||
@@ -839,11 +614,9 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -857,11 +630,9 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -873,11 +644,9 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(String expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -891,11 +660,9 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -907,11 +674,9 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(Pattern expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -925,11 +690,9 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -941,11 +704,9 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void hasClass(String[] expected, HasClassOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -959,11 +720,9 @@ public interface LocatorAssertions {
|
||||
hasClass(expected, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a relaxed
|
||||
* regular expression.
|
||||
* Ensures the {@code Locator} points to an element with given CSS class.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
* assertThat(page.locator("#component")).hasClass("selected row");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
@@ -997,7 +756,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
|
||||
* assertThat(page.locator("button")).hasCSS("display", "flex");
|
||||
* }</pre>
|
||||
*
|
||||
* @param name CSS property name.
|
||||
@@ -1009,7 +768,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
|
||||
* assertThat(page.locator("button")).hasCSS("display", "flex");
|
||||
* }</pre>
|
||||
*
|
||||
* @param name CSS property name.
|
||||
@@ -1019,7 +778,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
|
||||
* assertThat(page.locator("button")).hasCSS("display", "flex");
|
||||
* }</pre>
|
||||
*
|
||||
* @param name CSS property name.
|
||||
@@ -1031,7 +790,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} resolves to an element with the given computed CSS style.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("button")).hasCSS("display", "flex");
|
||||
* assertThat(page.locator("button")).hasCSS("display", "flex");
|
||||
* }</pre>
|
||||
*
|
||||
* @param name CSS property name.
|
||||
@@ -1041,7 +800,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).hasId("lastname");
|
||||
* assertThat(page.locator("input")).hasId("lastname");
|
||||
* }</pre>
|
||||
*
|
||||
* @param id Element id.
|
||||
@@ -1052,7 +811,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).hasId("lastname");
|
||||
* assertThat(page.locator("input")).hasId("lastname");
|
||||
* }</pre>
|
||||
*
|
||||
* @param id Element id.
|
||||
@@ -1061,7 +820,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).hasId("lastname");
|
||||
* assertThat(page.locator("input")).hasId("lastname");
|
||||
* }</pre>
|
||||
*
|
||||
* @param id Element id.
|
||||
@@ -1072,7 +831,7 @@ public interface LocatorAssertions {
|
||||
/**
|
||||
* Ensures the {@code Locator} points to an element with the given DOM Node ID.
|
||||
* <pre>{@code
|
||||
* assertThat(page.getByRole("textbox")).hasId("lastname");
|
||||
* assertThat(page.locator("input")).hasId("lastname");
|
||||
* }</pre>
|
||||
*
|
||||
* @param id Element id.
|
||||
@@ -1109,28 +868,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1145,28 +885,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1179,28 +900,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1215,28 +917,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1249,28 +932,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1285,28 +949,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1319,28 +964,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1355,28 +981,9 @@ public interface LocatorAssertions {
|
||||
* assertThat(page.locator(".title")).hasText(Pattern.compile("Welcome, .*"));
|
||||
* }</pre>
|
||||
*
|
||||
* <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:
|
||||
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
* <pre>{@code
|
||||
* // ✓ 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"});
|
||||
* assertThat(page.locator("list > .component")).hasText(new String[] {"Text 1", "Text 2", "Text 3"});
|
||||
* }</pre>
|
||||
*
|
||||
* @param expected Expected substring or RegExp or a list of those.
|
||||
@@ -1426,61 +1033,5 @@ public interface LocatorAssertions {
|
||||
* @param value Expected value.
|
||||
*/
|
||||
void hasValue(Pattern value, HasValueOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
|
||||
* values are selected.
|
||||
*
|
||||
* <p> For example, given the following element:
|
||||
* <pre>{@code
|
||||
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
|
||||
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
|
||||
* }</pre>
|
||||
*
|
||||
* @param values Expected options currently selected.
|
||||
*/
|
||||
default void hasValues(String[] values) {
|
||||
hasValues(values, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
|
||||
* values are selected.
|
||||
*
|
||||
* <p> For example, given the following element:
|
||||
* <pre>{@code
|
||||
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
|
||||
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
|
||||
* }</pre>
|
||||
*
|
||||
* @param values Expected options currently selected.
|
||||
*/
|
||||
void hasValues(String[] values, HasValuesOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
|
||||
* values are selected.
|
||||
*
|
||||
* <p> For example, given the following element:
|
||||
* <pre>{@code
|
||||
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
|
||||
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
|
||||
* }</pre>
|
||||
*
|
||||
* @param values Expected options currently selected.
|
||||
*/
|
||||
default void hasValues(Pattern[] values) {
|
||||
hasValues(values, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to multi-select/combobox (i.e. a {@code select} with the {@code multiple} attribute) and the specified
|
||||
* values are selected.
|
||||
*
|
||||
* <p> For example, given the following element:
|
||||
* <pre>{@code
|
||||
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
|
||||
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
|
||||
* }</pre>
|
||||
*
|
||||
* @param values Expected options currently selected.
|
||||
*/
|
||||
void hasValues(Pattern[] values, HasValuesOptions options);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
|
||||
* @Test
|
||||
* void navigatesToLoginPage() {
|
||||
* ...
|
||||
* page.getByText("Sign in").click();
|
||||
* page.click("#login");
|
||||
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
|
||||
* }
|
||||
* }
|
||||
@@ -120,7 +120,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
default void hasURL(String urlOrRegExp) {
|
||||
hasURL(urlOrRegExp, null);
|
||||
@@ -131,7 +131,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
void hasURL(String urlOrRegExp, HasURLOptions options);
|
||||
/**
|
||||
@@ -140,7 +140,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
default void hasURL(Pattern urlOrRegExp) {
|
||||
hasURL(urlOrRegExp, null);
|
||||
@@ -151,7 +151,7 @@ public interface PageAssertions {
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected URL string or RegExp.
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
|
||||
}
|
||||
|
||||
+1
-14
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
@@ -38,7 +37,7 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
* @Test
|
||||
* void statusBecomesSubmitted() {
|
||||
* ...
|
||||
* page.locator("#submit-button").click();
|
||||
* page.click("#submit-button");
|
||||
* assertThat(page.locator(".status")).hasText("Submitted");
|
||||
* }
|
||||
* }
|
||||
@@ -87,17 +86,5 @@ 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,12 +110,6 @@ 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"));
|
||||
}
|
||||
|
||||
+1
-16
@@ -21,7 +21,6 @@ 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;
|
||||
@@ -55,20 +54,6 @@ public class APIResponseAssertionsImpl implements APIResponseAssertions {
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + 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);
|
||||
throw new AssertionFailedError(message + log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class AssertionsBase {
|
||||
|
||||
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
|
||||
if (expectOptions.timeout == null) {
|
||||
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
|
||||
expectOptions.timeout = 5_000.0;
|
||||
}
|
||||
if (expectOptions.isNot) {
|
||||
message = message.replace("expected to", "expected not to");
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -20,20 +20,21 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import com.microsoft.playwright.options.BindingCallback;
|
||||
import com.microsoft.playwright.options.Cookie;
|
||||
import com.microsoft.playwright.options.FunctionCallback;
|
||||
import com.microsoft.playwright.options.Geolocation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -53,17 +54,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
final Map<String, HarRecorder> harRecorders = new HashMap<>();
|
||||
|
||||
static class HarRecorder {
|
||||
final Path path;
|
||||
final HarContentPolicy contentPolicy;
|
||||
|
||||
HarRecorder(Path har, HarContentPolicy policy) {
|
||||
path = har;
|
||||
contentPolicy = policy;
|
||||
}
|
||||
}
|
||||
Path recordHarPath;
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
@@ -83,13 +74,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
tracing.isRemote = browser != null && browser.isRemote;
|
||||
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
|
||||
}
|
||||
|
||||
void setRecordHar(Path path, HarContentPolicy policy) {
|
||||
if (path != null) {
|
||||
harRecorders.put("", new HarRecorder(path, policy));
|
||||
}
|
||||
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
|
||||
}
|
||||
|
||||
void setBaseUrl(String spec) {
|
||||
@@ -170,7 +155,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public Page waitForPage(WaitForPageOptions options, Runnable code) {
|
||||
return withWaitLogging("BrowserContext.close", logger -> waitForPageImpl(options, code));
|
||||
return withWaitLogging("BrowserContext.close", () -> waitForPageImpl(options, code));
|
||||
}
|
||||
|
||||
private Page waitForPageImpl(WaitForPageOptions options, Runnable code) {
|
||||
@@ -196,31 +181,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
isClosedOrClosing = true;
|
||||
try {
|
||||
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", entry.getKey());
|
||||
JsonObject json = sendMessage("harExport", params).getAsJsonObject();
|
||||
if (recordHarPath != null) {
|
||||
JsonObject json = sendMessage("harExport").getAsJsonObject();
|
||||
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
// In case of CDP connection browser is null but since the connection is established by
|
||||
// the driver it is safe to consider the artifact local.
|
||||
if (browser() != null && browser().isRemote) {
|
||||
artifact.isRemote = true;
|
||||
}
|
||||
|
||||
// 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");
|
||||
boolean needCompressed = harParams.path.toString().endsWith(".zip");
|
||||
if (isCompressed && !needCompressed) {
|
||||
String tmpPath = harParams.path + ".tmp";
|
||||
artifact.saveAs(Paths.get(tmpPath));
|
||||
JsonObject unzipParams = new JsonObject();
|
||||
unzipParams.addProperty("zipFile", tmpPath);
|
||||
unzipParams.addProperty("harFile", harParams.path.toString());
|
||||
connection.localUtils.sendMessage("harUnzip", unzipParams);
|
||||
} else {
|
||||
artifact.saveAs(harParams.path);
|
||||
}
|
||||
artifact.saveAs(recordHarPath);
|
||||
artifact.delete();
|
||||
}
|
||||
|
||||
@@ -367,7 +336,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(baseUrl, url), handler, options);
|
||||
route(new UrlMatcher(this.baseUrl, url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -380,21 +349,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void routeFromHAR(Path har, RouteFromHAROptions options) {
|
||||
if (options == null) {
|
||||
options = new RouteFromHAROptions();
|
||||
}
|
||||
if (options.update != null && options.update) {
|
||||
recordIntoHar(null, har, options);
|
||||
return;
|
||||
}
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url);
|
||||
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
|
||||
onClose(context -> harRouter.dispose());
|
||||
route(matcher, route -> harRouter.handle(route), null);
|
||||
}
|
||||
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
|
||||
withLogging("BrowserContext.route", () -> {
|
||||
routes.add(matcher, handler, options == null ? null : options.times);
|
||||
@@ -406,22 +360,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
});
|
||||
}
|
||||
|
||||
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
|
||||
JsonObject params = new JsonObject();
|
||||
if (page != null) {
|
||||
params.add("page", page.toProtocolRef());
|
||||
}
|
||||
JsonObject jsonOptions = new JsonObject();
|
||||
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
|
||||
jsonOptions.addProperty("content", HarContentPolicy.ATTACH.name().toLowerCase());
|
||||
jsonOptions.addProperty("mode", HarMode.MINIMAL.name().toLowerCase());
|
||||
addHarUrlFilter(jsonOptions, options.url);
|
||||
params.add("options", jsonOptions);
|
||||
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
|
||||
String harId = json.get("harId").getAsString();
|
||||
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultNavigationTimeout(double timeout) {
|
||||
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
|
||||
@@ -536,12 +474,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
}
|
||||
|
||||
void handleRoute(RouteImpl route) {
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
void handleRoute(Route route) {
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
maybeDisableNetworkInterception();
|
||||
}
|
||||
if (!route.isHandled()){
|
||||
} else {
|
||||
route.resume();
|
||||
}
|
||||
}
|
||||
@@ -553,7 +490,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
@Override
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("route".equals(event)) {
|
||||
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
handleRoute(route);
|
||||
} else if ("page".equals(event)) {
|
||||
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
|
||||
@@ -623,11 +560,4 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
|
||||
WritableStream createTempFile(String name) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("name", name);
|
||||
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,20 @@ package com.microsoft.playwright.impl;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
import com.microsoft.playwright.Browser;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
|
||||
class BrowserImpl extends ChannelOwner implements Browser {
|
||||
final Set<BrowserContextImpl> contexts = new HashSet<>();
|
||||
@@ -41,7 +40,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
boolean isRemote;
|
||||
boolean isConnectedOverWebSocket;
|
||||
private boolean isConnected = true;
|
||||
BrowserTypeImpl browserType;
|
||||
LocalUtils localUtils;
|
||||
|
||||
enum EventType {
|
||||
DISCONNECTED,
|
||||
@@ -61,11 +60,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
listeners.remove(EventType.DISCONNECTED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserType browserType() {
|
||||
return browserType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
withLogging("Browser.close", () -> closeImpl());
|
||||
@@ -118,9 +112,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
private BrowserContextImpl newContextImpl(NewContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new NewContextOptions();
|
||||
} else {
|
||||
// Make a copy so that we can nullify some fields below.
|
||||
options = convertType(options, NewContextOptions.class);
|
||||
}
|
||||
if (options.storageStatePath != null) {
|
||||
try {
|
||||
@@ -136,50 +127,21 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
|
||||
options.storageState = null;
|
||||
}
|
||||
JsonObject recordHar = null;
|
||||
Path recordHarPath = options.recordHarPath;
|
||||
HarContentPolicy harContentPolicy = null;
|
||||
if (options.recordHarPath != null) {
|
||||
recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarContent != null) {
|
||||
harContentPolicy = options.recordHarContent;
|
||||
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
|
||||
harContentPolicy = HarContentPolicy.OMIT;
|
||||
}
|
||||
if (harContentPolicy != null) {
|
||||
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
|
||||
}
|
||||
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
|
||||
options.recordHarPath = null;
|
||||
options.recordHarMode = null;
|
||||
options.recordHarOmitContent = null;
|
||||
options.recordHarContent = null;
|
||||
options.recordHarUrlFilter = null;
|
||||
} else {
|
||||
if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarUrlFilter != null) {
|
||||
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarContent != null) {
|
||||
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (storageState != null) {
|
||||
params.add("storageState", storageState);
|
||||
}
|
||||
if (recordHar != null) {
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
@@ -209,7 +171,8 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.setRecordHar(recordHarPath, harContentPolicy);
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -230,7 +193,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (page != null) {
|
||||
params.add("page", ((PageImpl) page).toProtocolRef());
|
||||
JsonObject jsonPage = new JsonObject();
|
||||
jsonPage.addProperty("guid", ((PageImpl) page).guid);
|
||||
params.add("page", jsonPage);
|
||||
}
|
||||
sendMessage("startTracing", params);
|
||||
}
|
||||
|
||||
@@ -22,15 +22,12 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Browser;
|
||||
import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
LocalUtils localUtils;
|
||||
@@ -51,7 +48,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonElement result = sendMessage("launch", params);
|
||||
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.browserType = this;
|
||||
browser.localUtils = localUtils;
|
||||
return browser;
|
||||
}
|
||||
|
||||
@@ -83,9 +80,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
headers.addProperty("x-playwright-browser", name());
|
||||
}
|
||||
|
||||
JsonObject json = connection.localUtils().sendMessage("connect", params).getAsJsonObject();
|
||||
JsonObject json = sendMessage("connect", params).getAsJsonObject();
|
||||
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
|
||||
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
|
||||
Connection connection = new Connection(pipe);
|
||||
PlaywrightImpl playwright = connection.initializePlaywright();
|
||||
if (!playwright.initializer.has("preLaunchedBrowser")) {
|
||||
try {
|
||||
@@ -99,7 +96,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.isConnectedOverWebSocket = true;
|
||||
browser.browserType = this;
|
||||
browser.localUtils = localUtils;
|
||||
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
pipe.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
@@ -133,7 +130,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
|
||||
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.browserType = this;
|
||||
browser.localUtils = localUtils;
|
||||
if (json.has("defaultContext")) {
|
||||
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
|
||||
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
|
||||
@@ -155,52 +152,20 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new LaunchPersistentContextOptions();
|
||||
} else {
|
||||
// Make a copy so that we can nullify some fields below.
|
||||
options = convertType(options, LaunchPersistentContextOptions.class);
|
||||
}
|
||||
JsonObject recordHar = null;
|
||||
Path recordHarPath = options.recordHarPath;
|
||||
HarContentPolicy harContentPolicy = null;
|
||||
if (options.recordHarPath != null) {
|
||||
recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarContent != null) {
|
||||
harContentPolicy = options.recordHarContent;
|
||||
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
|
||||
harContentPolicy = HarContentPolicy.OMIT;
|
||||
}
|
||||
if (harContentPolicy != null) {
|
||||
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
|
||||
}
|
||||
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
|
||||
options.recordHarPath = null;
|
||||
options.recordHarMode = null;
|
||||
options.recordHarOmitContent = null;
|
||||
options.recordHarContent = null;
|
||||
options.recordHarUrlFilter = null;
|
||||
} else {
|
||||
if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarUrlFilter != null) {
|
||||
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarContent != null) {
|
||||
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("userDataDir", userDataDir.toString());
|
||||
if (recordHar != null) {
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
@@ -230,7 +195,8 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.setRecordHar(recordHarPath, harContentPolicy);
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,11 @@ 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 ChannelOwner parent;
|
||||
private final ChannelOwner parent;
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
|
||||
final String type;
|
||||
@@ -70,13 +68,7 @@ class ChannelOwner extends LoggingSupport {
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
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) {
|
||||
<T> T withWaitLogging(String apiName, Supplier<T> code) {
|
||||
return new WaitForEventLogger<>(this, apiName, code).get();
|
||||
}
|
||||
|
||||
@@ -116,10 +108,4 @@ class ChannelOwner extends LoggingSupport {
|
||||
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
}
|
||||
|
||||
JsonObject toProtocolRef() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ public class Connection {
|
||||
String debug = System.getenv("DEBUG");
|
||||
isLogging = (debug != null) && debug.contains("pw:channel");
|
||||
}
|
||||
LocalUtils localUtils;
|
||||
final Map<String, String> env;
|
||||
|
||||
class Root extends ChannelOwner {
|
||||
Root(Connection connection) {
|
||||
@@ -80,19 +78,13 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
Connection(Transport pipe, Map<String, String> env, LocalUtils localUtils) {
|
||||
this(pipe, env);
|
||||
this.localUtils = localUtils;
|
||||
}
|
||||
|
||||
Connection(Transport transport, Map<String, String> env) {
|
||||
this.env = env;
|
||||
Connection(Transport transport) {
|
||||
if (isLogging) {
|
||||
transport = new TransportLogger(transport);
|
||||
}
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv(env);
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv();
|
||||
}
|
||||
|
||||
boolean isCollectingStacks() {
|
||||
@@ -146,10 +138,6 @@ public class Connection {
|
||||
return (PlaywrightImpl) this.root.initialize();
|
||||
}
|
||||
|
||||
LocalUtils localUtils() {
|
||||
return localUtils;
|
||||
}
|
||||
|
||||
public <T> T getExistingObject(String guid) {
|
||||
@SuppressWarnings("unchecked") T result = (T) objects.get(guid);
|
||||
if (result == null)
|
||||
@@ -206,24 +194,18 @@ 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);
|
||||
}
|
||||
|
||||
@@ -289,9 +271,6 @@ public class Connection {
|
||||
break;
|
||||
case "LocalUtils":
|
||||
result = new LocalUtils(parent, type, guid, initializer);
|
||||
if (localUtils == null) {
|
||||
localUtils = (LocalUtils) result;
|
||||
}
|
||||
break;
|
||||
case "Page":
|
||||
result = new PageImpl(parent, type, guid, initializer);
|
||||
@@ -323,9 +302,6 @@ public class Connection {
|
||||
case "Worker":
|
||||
result = new WorkerImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "WritableStream":
|
||||
result = new WritableStream(parent, type, guid, initializer);
|
||||
break;
|
||||
default:
|
||||
throw new PlaywrightException("Unknown type " + type);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
|
||||
@@ -300,7 +299,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameImpl ownerFrame() {
|
||||
public Frame ownerFrame() {
|
||||
return withLogging("ElementHandle.ownerFrame", () -> {
|
||||
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
|
||||
if (!json.has("frame")) {
|
||||
@@ -456,24 +455,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
|
||||
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
|
||||
FrameImpl frame = ownerFrame();
|
||||
if (frame == null) {
|
||||
throw new Error("Cannot set input files to detached element");
|
||||
}
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, frame.page().context());
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
setInputFiles(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -487,7 +469,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ class FileChooserImpl implements FileChooser {
|
||||
|
||||
@Override
|
||||
public void setFiles(Path[] files, SetFilesOptions options) {
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
setFiles(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,6 +69,6 @@ class FileChooserImpl implements FileChooser {
|
||||
@Override
|
||||
public void setFiles(FilePayload[] files, SetFilesOptions options) {
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +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.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.options.WaitUntilState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
|
||||
@@ -374,66 +373,6 @@ 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();
|
||||
@@ -747,32 +686,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, page.context());
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
@@ -872,17 +800,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withWaitLogging("Frame.waitForLoadState", logger -> {
|
||||
waitForLoadStateImpl(state, options, logger);
|
||||
withWaitLogging("Frame.waitForLoadState", () -> {
|
||||
waitForLoadStateImpl(state, options);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options, Logger logger) {
|
||||
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options, logger);
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
|
||||
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options);
|
||||
}
|
||||
|
||||
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options, Logger logger) {
|
||||
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForLoadStateOptions();
|
||||
}
|
||||
@@ -891,7 +819,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
List<Waitable<Void>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitForLoadStateHelper(state, logger));
|
||||
waitables.add(new WaitForLoadStateHelper(state));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableTimeout(options.timeout));
|
||||
runUntil(() -> {}, new WaitableRace<>(waitables));
|
||||
@@ -899,12 +827,10 @@ 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, Logger logger) {
|
||||
WaitForLoadStateHelper(WaitUntilState state) {
|
||||
expectedState = state;
|
||||
this.logger = logger;
|
||||
isDone = loadStates.contains(state);
|
||||
if (!isDone) {
|
||||
internalListeners.add(InternalEventType.LOADSTATE, this);
|
||||
@@ -913,7 +839,6 @@ 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();
|
||||
@@ -937,24 +862,20 @@ 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, Logger logger) {
|
||||
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
|
||||
this.matcher = matcher;
|
||||
this.expectedLoadState = expectedLoadState;
|
||||
this.logger = logger;
|
||||
internalListeners.add(InternalEventType.NAVIGATED, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(JsonObject params) {
|
||||
String url = params.get("url").getAsString();
|
||||
logger.log(" navigated to " + url);
|
||||
if (!matcher.test(url)) {
|
||||
if (!matcher.test(params.get("url").getAsString())) {
|
||||
return;
|
||||
}
|
||||
if (params.has("error")) {
|
||||
@@ -966,7 +887,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
request = connection.getExistingObject(jsonReq.get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState, logger);
|
||||
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState);
|
||||
}
|
||||
internalListeners.remove(InternalEventType.NAVIGATED, this);
|
||||
}
|
||||
@@ -1005,14 +926,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withWaitLogging("Frame.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options, null));
|
||||
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
|
||||
}
|
||||
|
||||
Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
|
||||
return waitForNavigationImpl(logger, code, options, null);
|
||||
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
|
||||
return waitForNavigationImpl(code, options, null);
|
||||
}
|
||||
|
||||
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
|
||||
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
|
||||
if (options == null) {
|
||||
options = new WaitForNavigationOptions();
|
||||
}
|
||||
@@ -1024,8 +945,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
if (matcher == null) {
|
||||
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
|
||||
}
|
||||
logger.log("waiting for navigation " + matcher);
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||
@@ -1083,22 +1003,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withWaitLogging("Frame.waitForURL", logger -> {
|
||||
waitForURLImpl(logger, matcher, options);
|
||||
return null;
|
||||
});
|
||||
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
|
||||
}
|
||||
|
||||
void waitForURLImpl(Logger logger, UrlMatcher matcher, WaitForURLOptions options) {
|
||||
logger.log("waiting for url " + matcher);
|
||||
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForURLOptions();
|
||||
}
|
||||
if (matcher.test(url())) {
|
||||
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class), logger);
|
||||
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
|
||||
return;
|
||||
}
|
||||
waitForNavigationImpl(logger, () -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
|
||||
waitForNavigationImpl(() -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
|
||||
}
|
||||
|
||||
int queryCount(String selector) {
|
||||
@@ -1120,13 +1036,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
if (add != null) {
|
||||
WaitUntilState state = loadStateFromProtocol(add.getAsString());
|
||||
loadStates.add(state);
|
||||
if (parentFrame == null && page != null) {
|
||||
if (state == LOAD) {
|
||||
page.listeners.notify(PageImpl.EventType.LOAD, page);
|
||||
} else if (state == DOMCONTENTLOADED) {
|
||||
page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page);
|
||||
}
|
||||
}
|
||||
internalListeners.notify(InternalEventType.LOADSTATE, state);
|
||||
}
|
||||
JsonElement remove = params.get("remove");
|
||||
|
||||
@@ -18,11 +18,7 @@ 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 {
|
||||
@@ -41,67 +37,7 @@ class FrameLocatorImpl implements FrameLocator {
|
||||
|
||||
@Override
|
||||
public FrameLocatorImpl frameLocator(String 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)));
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +47,7 @@ class FrameLocatorImpl implements FrameLocator {
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(frame, frameSelector + " >> internal:control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
|
||||
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Route;
|
||||
import com.microsoft.playwright.options.HarNotFound;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
public class HARRouter {
|
||||
private final LocalUtils localUtils;
|
||||
private final HarNotFound defaultAction;
|
||||
private final String harId;
|
||||
|
||||
HARRouter(LocalUtils localUtils, Path harFile, HarNotFound defaultAction) {
|
||||
this.localUtils = localUtils;
|
||||
this.defaultAction = defaultAction;
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("file", harFile.toString());
|
||||
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject();
|
||||
if (json.has("error")) {
|
||||
throw new PlaywrightException(json.get("error").getAsString());
|
||||
}
|
||||
harId = json.get("harId").getAsString();
|
||||
}
|
||||
|
||||
void handle(Route route) {
|
||||
Request request = route.request();
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", harId);
|
||||
params.addProperty("url", request.url());
|
||||
params.addProperty("method", request.method());
|
||||
params.add("headers", gson().toJsonTree(request.headersArray()));
|
||||
if (request.postDataBuffer() != null) {
|
||||
String base64 = Base64.getEncoder().encodeToString(request.postDataBuffer());
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
params.addProperty("isNavigationRequest", request.isNavigationRequest());
|
||||
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject();
|
||||
|
||||
String action = response.get("action").getAsString();
|
||||
if ("redirect".equals(action)) {
|
||||
String redirectURL = response.get("redirectURL").getAsString();
|
||||
logApiIfEnabled("HAR: " + route.request().url() + " redirected to " + redirectURL);
|
||||
((RouteImpl) route).redirectNavigationRequest(redirectURL);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("fulfill".equals(action)) {
|
||||
int status = response.get("status").getAsInt();
|
||||
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
|
||||
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(status)
|
||||
.setHeaders(headers)
|
||||
.setBodyBytes(buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
if ("error".equals(action)) {
|
||||
logApiIfEnabled("HAR: " + response.get("message").getAsString());
|
||||
// Report the error, but fall through to the default handler.
|
||||
}
|
||||
|
||||
if (defaultAction == HarNotFound.FALLBACK) {
|
||||
route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// By default abort not matching requests.
|
||||
route.abort();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", harId);
|
||||
localUtils.sendMessageAsync("harClose", params);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.assertions.LocatorAssertions;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -40,7 +39,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
public void containsText(String text, ContainsTextOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
|
||||
@@ -49,7 +47,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
@Override
|
||||
public void containsText(Pattern pattern, ContainsTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class));
|
||||
@@ -61,7 +58,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
for (String text : strings) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
@@ -74,7 +70,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
@@ -208,7 +203,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
public void hasText(String text, HasTextOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
|
||||
@@ -217,7 +211,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
@Override
|
||||
public void hasText(Pattern pattern, HasTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
// Just match substring, same as containsText.
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
@@ -230,7 +223,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
for (String text : strings) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
@@ -243,7 +235,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
@@ -264,32 +255,9 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValues(String[] values, HasValuesOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (String text : values) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValues(Pattern[] patterns, HasValuesOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.matchSubstring = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isChecked(IsCheckedOptions options) {
|
||||
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
|
||||
expectTrue(expression, "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
|
||||
expectTrue("to.be.checked", "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,9 +267,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
|
||||
@Override
|
||||
public void isEditable(IsEditableOptions options) {
|
||||
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);
|
||||
expectTrue("to.be.editable", "Locator expected to be editable", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,9 +277,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
|
||||
@Override
|
||||
public void isEnabled(IsEnabledOptions options) {
|
||||
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);
|
||||
expectTrue("to.be.enabled", "Locator expected to be enabled", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,9 +292,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
|
||||
@Override
|
||||
public void isVisible(IsVisibleOptions options) {
|
||||
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);
|
||||
expectTrue("to.be.visible", "Locator expected to be visible", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
private void expectTrue(String expression, String message, FrameExpectOptions options) {
|
||||
@@ -342,17 +304,5 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
public LocatorAssertions not() {
|
||||
return new LocatorAssertionsImpl(actualLocator, !isNot);
|
||||
}
|
||||
|
||||
private static Boolean shouldIgnoreCase(Object options) {
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Field fromField = options.getClass().getDeclaredField("ignoreCase");
|
||||
Object value = fromField.get(options);
|
||||
return (Boolean) value;
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
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 java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
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;
|
||||
@@ -26,15 +26,22 @@ class LocatorImpl implements Locator {
|
||||
this.frame = frame;
|
||||
if (options != null) {
|
||||
if (options.hasText != null) {
|
||||
String textSelector = "text=" + escapeForTextSelector(options.hasText, false);
|
||||
selector += " >> internal:has=" + gson().toJson(textSelector);
|
||||
if (options.hasText instanceof Pattern) {
|
||||
Pattern pattern = (Pattern) options.hasText;
|
||||
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
|
||||
} else if (options.hasText instanceof String) {
|
||||
String text = (String) options.hasText;
|
||||
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
|
||||
}
|
||||
}
|
||||
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);
|
||||
LocatorImpl has = (LocatorImpl) options.has;
|
||||
if (has.frame != frame) {
|
||||
throw new PlaywrightException("Inner 'has' locator must belong to the same frame.");
|
||||
}
|
||||
selector += " >> has=" + gson().toJson(has.selector);
|
||||
}
|
||||
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
@@ -163,11 +170,6 @@ class LocatorImpl implements Locator {
|
||||
frame.fill(selector, value, convertType(options, Frame.FillOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator filter(FilterOptions options) {
|
||||
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator first() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=0", null);
|
||||
@@ -194,66 +196,6 @@ 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 null;
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -531,7 +473,9 @@ class LocatorImpl implements Locator {
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject result = new JsonObject();
|
||||
result.add("frame", frame.toProtocolRef());
|
||||
JsonObject frameJson = new JsonObject();
|
||||
frameJson.addProperty("guid", frame.guid);
|
||||
result.add("frame", frameJson);
|
||||
result.addProperty("selector", selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
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(String text, Locator.GetByTextOptions options) {
|
||||
boolean exact = options != null && options.exact != null && options.exact;
|
||||
return "text=" + escapeForTextSelector(text, exact);
|
||||
}
|
||||
|
||||
static String getByTextSelector(Pattern text, Locator.GetByTextOptions options) {
|
||||
boolean exact = options != null && options.exact != null && options.exact;
|
||||
return "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("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, false);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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,13 +60,7 @@ class LoggingSupport {
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
|
||||
static void logApiIfEnabled(String message) {
|
||||
if (isEnabled) {
|
||||
logApi(message);
|
||||
}
|
||||
}
|
||||
|
||||
static void logApi(String message) {
|
||||
private void logApi(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:api " + message);
|
||||
}
|
||||
|
||||
@@ -175,6 +175,10 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("load".equals(event)) {
|
||||
listeners.notify(EventType.LOAD, this);
|
||||
} else if ("domcontentloaded".equals(event)) {
|
||||
listeners.notify(EventType.DOMCONTENTLOADED, this);
|
||||
} else if ("frameAttached".equals(event)) {
|
||||
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
|
||||
FrameImpl frame = connection.getExistingObject(guid);
|
||||
@@ -194,12 +198,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
listeners.notify(EventType.FRAMEDETACHED, frame);
|
||||
} else if ("route".equals(event)) {
|
||||
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
maybeDisableNetworkInterception();
|
||||
}
|
||||
if (!route.isHandled()) {
|
||||
} else {
|
||||
browserContext.handleRoute(route);
|
||||
}
|
||||
} else if ("video".equals(event)) {
|
||||
@@ -443,7 +446,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForClose", logger -> waitForCloseImpl(options, code));
|
||||
return withWaitLogging("Page.waitForClose", () -> waitForCloseImpl(options, code));
|
||||
}
|
||||
|
||||
private Page waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
|
||||
@@ -455,7 +458,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
|
||||
return withWaitLogging("Page.waitForConsoleMessage", () -> waitForConsoleMessageImpl(options, code));
|
||||
}
|
||||
|
||||
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
|
||||
@@ -467,7 +470,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Download waitForDownload(WaitForDownloadOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForDownload", logger -> waitForDownloadImpl(options, code));
|
||||
return withWaitLogging("Page.waitForDownload", () -> waitForDownloadImpl(options, code));
|
||||
}
|
||||
|
||||
private Download waitForDownloadImpl(WaitForDownloadOptions options, Runnable code) {
|
||||
@@ -479,7 +482,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public FileChooser waitForFileChooser(WaitForFileChooserOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForFileChooser", logger -> waitForFileChooserImpl(options, code));
|
||||
return withWaitLogging("Page.waitForFileChooser", () -> waitForFileChooserImpl(options, code));
|
||||
}
|
||||
|
||||
private FileChooser waitForFileChooserImpl(WaitForFileChooserOptions options, Runnable code) {
|
||||
@@ -492,7 +495,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Page waitForPopup(WaitForPopupOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForPopup", logger -> waitForPopupImpl(options, code));
|
||||
return withWaitLogging("Page.waitForPopup", () -> waitForPopupImpl(options, code));
|
||||
}
|
||||
|
||||
private Page waitForPopupImpl(WaitForPopupOptions options, Runnable code) {
|
||||
@@ -504,7 +507,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public WebSocket waitForWebSocket(WaitForWebSocketOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForWebSocket", logger -> waitForWebSocketImpl(options, code));
|
||||
return withWaitLogging("Page.waitForWebSocket", () -> waitForWebSocketImpl(options, code));
|
||||
}
|
||||
|
||||
private WebSocket waitForWebSocketImpl(WaitForWebSocketOptions options, Runnable code) {
|
||||
@@ -516,7 +519,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Worker waitForWorker(WaitForWorkerOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForWorker", logger -> waitForWorkerImpl(options, code));
|
||||
return withWaitLogging("Page.waitForWorker", () -> waitForWorkerImpl(options, code));
|
||||
}
|
||||
|
||||
private Worker waitForWorkerImpl(WaitForWorkerOptions options, Runnable code) {
|
||||
@@ -761,77 +764,6 @@ 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));
|
||||
@@ -1040,21 +972,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void routeFromHAR(Path har, RouteFromHAROptions options) {
|
||||
if (options == null) {
|
||||
options = new RouteFromHAROptions();
|
||||
}
|
||||
if (options.update != null && options.update) {
|
||||
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
|
||||
return;
|
||||
}
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url);
|
||||
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
|
||||
onClose(context -> harRouter.dispose());
|
||||
route(matcher, route -> harRouter.handle(route), null);
|
||||
}
|
||||
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
|
||||
withLogging("Page.route", () -> {
|
||||
routes.add(matcher, handler, options == null ? null : options.times);
|
||||
@@ -1339,25 +1256,25 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withWaitLogging("Page.waitForLoadState", logger -> {
|
||||
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
|
||||
withWaitLogging("Page.waitForLoadState", () -> {
|
||||
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options));
|
||||
return withLogging("Page.waitForNavigation", () -> waitForNavigationImpl(code, options));
|
||||
}
|
||||
|
||||
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
|
||||
Response waitForNavigationImpl(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(logger, code, frameOptions);
|
||||
return mainFrame.waitForNavigationImpl(code, frameOptions);
|
||||
}
|
||||
|
||||
void frameNavigated(FrameImpl frame) {
|
||||
@@ -1409,28 +1326,21 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
|
||||
return waitForRequest(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
|
||||
return waitForRequest(toRequestPredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(Pattern urlPattern, WaitForRequestOptions options, Runnable code) {
|
||||
return waitForRequest(new UrlMatcher(urlPattern), null, options, code);
|
||||
return waitForRequest(toRequestPredicate(new UrlMatcher(urlPattern)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
|
||||
return waitForRequest(null, predicate, options, code);
|
||||
return withWaitLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
|
||||
}
|
||||
|
||||
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 static Predicate<Request> toRequestPredicate(UrlMatcher matcher) {
|
||||
return request -> matcher.test(request.url());
|
||||
}
|
||||
|
||||
private Request waitForRequestImpl(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
|
||||
@@ -1442,7 +1352,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Request waitForRequestFinished(WaitForRequestFinishedOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForRequestFinished", logger -> waitForRequestFinishedImpl(options, code));
|
||||
return withWaitLogging("Page.waitForRequestFinished", () -> waitForRequestFinishedImpl(options, code));
|
||||
}
|
||||
|
||||
private Request waitForRequestFinishedImpl(WaitForRequestFinishedOptions options, Runnable code) {
|
||||
@@ -1454,28 +1364,21 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
|
||||
return waitForResponse(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
|
||||
return waitForResponse(toResponsePredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForResponse(Pattern urlPattern, WaitForResponseOptions options, Runnable code) {
|
||||
return waitForResponse(new UrlMatcher(urlPattern), null, options, code);
|
||||
return waitForResponse(toResponsePredicate(new UrlMatcher(urlPattern)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForResponse(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
|
||||
return waitForResponse(null, predicate, options, code);
|
||||
return withLogging("Page.waitForResponse", () -> waitForResponseImpl(predicate, options, code));
|
||||
}
|
||||
|
||||
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 static Predicate<Response> toResponsePredicate(UrlMatcher matcher) {
|
||||
return response -> matcher.test(response.url());
|
||||
}
|
||||
|
||||
private Response waitForResponseImpl(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
|
||||
@@ -1512,10 +1415,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withWaitLogging("Page.waitForURL", logger -> {
|
||||
mainFrame.waitForURLImpl(logger, matcher, convertType(options, Frame.WaitForURLOptions.class));
|
||||
return null;
|
||||
});
|
||||
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertType(options, Frame.WaitForURLOptions.class)));
|
||||
}
|
||||
|
||||
@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,23 +32,18 @@ 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 {
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("run-driver");
|
||||
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");
|
||||
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);
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
result.driverProcess = p;
|
||||
result.initSharedSelectors(null);
|
||||
|
||||
@@ -31,7 +31,6 @@ class SerializedValue{
|
||||
// Possible values: { 'null, 'undefined, 'NaN, 'Infinity, '-Infinity, '-0 }
|
||||
String v;
|
||||
String d;
|
||||
String u;
|
||||
public static class R {
|
||||
String p;
|
||||
String f;
|
||||
@@ -44,8 +43,6 @@ class SerializedValue{
|
||||
}
|
||||
O[] o;
|
||||
Number h;
|
||||
Integer id;
|
||||
Integer ref;
|
||||
}
|
||||
|
||||
class SerializedArgument{
|
||||
@@ -86,7 +83,6 @@ class ExpectedTextValue {
|
||||
String string;
|
||||
String regexSource;
|
||||
String regexFlags;
|
||||
Boolean ignoreCase;
|
||||
Boolean matchSubstring;
|
||||
Boolean normalizeWhiteSpace;
|
||||
}
|
||||
|
||||
@@ -42,14 +42,6 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
String failure;
|
||||
Timing timing;
|
||||
boolean didFailOrFinish;
|
||||
private FallbackOverrides fallbackOverrides;
|
||||
|
||||
static class FallbackOverrides {
|
||||
String url;
|
||||
String method;
|
||||
byte[] postData;
|
||||
Map<String, String> headers;
|
||||
}
|
||||
|
||||
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -83,9 +75,6 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
|
||||
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers)).headers();
|
||||
}
|
||||
return headers.headers();
|
||||
}
|
||||
|
||||
@@ -106,26 +95,19 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.method != null) {
|
||||
return fallbackOverrides.method;
|
||||
}
|
||||
return initializer.get("method").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postData() {
|
||||
byte[] buffer = postDataBuffer();
|
||||
if (buffer == null) {
|
||||
if (postData == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(buffer, StandardCharsets.UTF_8);
|
||||
return new String(postData, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] postDataBuffer() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.postData != null) {
|
||||
return fallbackOverrides.postData;
|
||||
}
|
||||
return postData;
|
||||
}
|
||||
|
||||
@@ -174,9 +156,6 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.url != null) {
|
||||
return fallbackOverrides.url;
|
||||
}
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
@@ -185,9 +164,6 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
}
|
||||
|
||||
private RawHeaders getRawHeaders() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
|
||||
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers));
|
||||
}
|
||||
if (rawHeaders != null) {
|
||||
return rawHeaders;
|
||||
}
|
||||
@@ -200,26 +176,4 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
|
||||
return rawHeaders;
|
||||
}
|
||||
|
||||
void applyFallbackOverrides(FallbackOverrides overrides) {
|
||||
if (fallbackOverrides == null) {
|
||||
fallbackOverrides = new FallbackOverrides();
|
||||
}
|
||||
if (overrides.url != null) {
|
||||
fallbackOverrides.url = overrides.url;
|
||||
}
|
||||
if (overrides.method != null) {
|
||||
fallbackOverrides.method = overrides.method;
|
||||
}
|
||||
if (overrides.headers != null) {
|
||||
fallbackOverrides.headers = overrides.headers;
|
||||
}
|
||||
if (overrides.postData != null) {
|
||||
fallbackOverrides.postData = overrides.postData;
|
||||
}
|
||||
}
|
||||
|
||||
FallbackOverrides fallbackOverridesForResume() {
|
||||
return fallbackOverrides;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ public class RequestOptionsImpl implements RequestOptions {
|
||||
Boolean failOnStatusCode;
|
||||
Boolean ignoreHTTPSErrors;
|
||||
Double timeout;
|
||||
Integer maxRedirects;
|
||||
|
||||
@Override
|
||||
public RequestOptions setHeader(String name, String value) {
|
||||
@@ -119,10 +118,4 @@ public class RequestOptionsImpl implements RequestOptions {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMaxRedirects(int maxRedirects) {
|
||||
this.maxRedirects = maxRedirects;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +83,6 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
return request().frame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fromServiceWorker() {
|
||||
return initializer.get("fromServiceWorker").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return headers.headers();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
@@ -28,15 +27,11 @@ import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
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
|
||||
@@ -49,69 +44,41 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
});
|
||||
}
|
||||
|
||||
boolean isHandled() {
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(ResumeOptions options) {
|
||||
startHandling();
|
||||
applyOverrides(convertType(options, FallbackOptions.class));
|
||||
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume()));
|
||||
withLogging("Route.resume", () -> resumeImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fallback(FallbackOptions options) {
|
||||
if (handled) {
|
||||
throw new PlaywrightException("Route is already handled!");
|
||||
}
|
||||
applyOverrides(options);
|
||||
}
|
||||
|
||||
private void applyOverrides(FallbackOptions options) {
|
||||
private void resumeImpl(ResumeOptions options) {
|
||||
if (options == null) {
|
||||
return;
|
||||
options = new ResumeOptions();
|
||||
}
|
||||
RequestImpl.FallbackOverrides overrides = new RequestImpl.FallbackOverrides();
|
||||
overrides.url = options.url;
|
||||
overrides.method = options.method;
|
||||
overrides.headers = options.headers;
|
||||
if (options.postData != null) {
|
||||
overrides.postData = getPostDataBytes(options.postData);
|
||||
}
|
||||
request().applyFallbackOverrides(overrides);
|
||||
}
|
||||
|
||||
private void resumeImpl(RequestImpl.FallbackOverrides options) {
|
||||
JsonObject params = new JsonObject();
|
||||
if (options != null) {
|
||||
if (options.url != null) {
|
||||
params.addProperty("url", options.url);
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", Serialization.toProtocol(options.headers));
|
||||
}
|
||||
if (options.postData != null) {
|
||||
String base64 = Base64.getEncoder().encodeToString(options.postData);
|
||||
params.addProperty("postData", base64);
|
||||
if (options.url != null) {
|
||||
params.addProperty("url", options.url);
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", Serialization.toProtocol(options.headers));
|
||||
}
|
||||
if (options.postData != null) {
|
||||
byte[] bytes = null;
|
||||
if (options.postData instanceof byte[]) {
|
||||
bytes = (byte[]) options.postData;
|
||||
} else if (options.postData instanceof String) {
|
||||
bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName());
|
||||
}
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
sendMessageAsync("continue", params);
|
||||
}
|
||||
|
||||
private static byte[] getPostDataBytes(Object postData) {
|
||||
if (postData instanceof byte[]) {
|
||||
return (byte[]) postData;
|
||||
}
|
||||
if (postData instanceof String) {
|
||||
return ((String) postData).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
throw new PlaywrightException("postData must be either String or byte[], found: " + postData.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fulfill(FulfillOptions options) {
|
||||
startHandling();
|
||||
@@ -198,15 +165,7 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
|
||||
@Override
|
||||
public RequestImpl request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
void redirectNavigationRequest(String redirectURL) {
|
||||
startHandling();
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("url", redirectURL);
|
||||
// TODO: _raceWithPageClose ?
|
||||
sendMessageAsync("redirectNavigationRequest", params);
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
|
||||
}
|
||||
|
||||
private void startHandling() {
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -38,16 +37,22 @@ class Router {
|
||||
this.times = times;
|
||||
}
|
||||
|
||||
void handle(RouteImpl route) {
|
||||
handler.accept(route);
|
||||
}
|
||||
|
||||
boolean decrementRemainingCallCount() {
|
||||
if (times == null) {
|
||||
boolean handle(Route route) {
|
||||
if (times != null && times <= 0) {
|
||||
return false;
|
||||
}
|
||||
--times;
|
||||
return times <= 0;
|
||||
if (!matcher.test(route.request().url())) {
|
||||
return false;
|
||||
}
|
||||
if (times != null) {
|
||||
--times;
|
||||
}
|
||||
handler.accept(route);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isDone() {
|
||||
return times != null && times <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,23 +70,15 @@ class Router {
|
||||
return routes.size();
|
||||
}
|
||||
|
||||
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
|
||||
HandleResult handle(RouteImpl route) {
|
||||
HandleResult result = HandleResult.NoMatchingHandler;
|
||||
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
|
||||
RouteInfo info = it.next();
|
||||
if (!info.matcher.test(route.request().url())) {
|
||||
continue;
|
||||
}
|
||||
if (info.decrementRemainingCallCount()) {
|
||||
it.remove();
|
||||
}
|
||||
result = HandleResult.FoundMatchingHandler;
|
||||
info.handle(route);
|
||||
if (route.isHandled()) {
|
||||
break;
|
||||
boolean handle(Route route) {
|
||||
for (RouteInfo info : routes) {
|
||||
if (info.handle(route)) {
|
||||
if (info.isDone()) {
|
||||
routes.remove(info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,21 +28,12 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.MalformedURLException;
|
||||
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;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
import static com.microsoft.playwright.impl.Utils.fromJsRegexFlags;
|
||||
|
||||
class Serialization {
|
||||
private static final Gson gson = new GsonBuilder().disableHtmlEscaping()
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
|
||||
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
|
||||
@@ -51,9 +42,6 @@ class Serialization {
|
||||
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
|
||||
.registerTypeAdapter(ScreenshotAnimations.class, new ToLowerCaseSerializer<ScreenshotAnimations>())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(ScreenshotScale.class, new ToLowerCaseSerializer<ScreenshotScale>())
|
||||
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
|
||||
.registerTypeAdapter(ServiceWorkerPolicy.class, new ToLowerCaseAndDashSerializer<ServiceWorkerPolicy>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
@@ -81,138 +69,82 @@ class Serialization {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class ValueSerializer {
|
||||
// hashCode() of a map containing itself as a key will throw stackoverflow exception,
|
||||
// so we user wrappers.
|
||||
private static class HashableValue {
|
||||
final Object value;
|
||||
HashableValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return value == ((HashableValue) o).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(value);
|
||||
}
|
||||
private static SerializedValue serializeValue(Object value, List<JSHandleImpl> handles, int depth) {
|
||||
if (depth > 100) {
|
||||
throw new PlaywrightException("Maximum argument depth exceeded");
|
||||
}
|
||||
private final Map<HashableValue, Integer> valueToId = new HashMap<>();
|
||||
private int lastId = 0;
|
||||
private final List<JSHandleImpl> handles = new ArrayList<>();
|
||||
private final SerializedValue serializedValue;
|
||||
|
||||
ValueSerializer(Object value) {
|
||||
serializedValue = serializeValue(value);
|
||||
}
|
||||
|
||||
SerializedArgument toSerializedArgument() {
|
||||
SerializedArgument result = new SerializedArgument();
|
||||
result.value = serializedValue;
|
||||
result.handles = new Channel[handles.size()];
|
||||
int i = 0;
|
||||
for (JSHandleImpl handle : handles) {
|
||||
result.handles[i] = new Channel();
|
||||
result.handles[i].guid = handle.guid;
|
||||
++i;
|
||||
}
|
||||
SerializedValue result = new SerializedValue();
|
||||
if (value instanceof JSHandleImpl) {
|
||||
result.h = handles.size();
|
||||
handles.add((JSHandleImpl) value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private SerializedValue serializeValue(Object value) {
|
||||
SerializedValue result = new SerializedValue();
|
||||
if (value instanceof JSHandleImpl) {
|
||||
result.h = handles.size();
|
||||
handles.add((JSHandleImpl) value);
|
||||
return result;
|
||||
}
|
||||
if (value == null) {
|
||||
result.v = "undefined";
|
||||
} else if (value instanceof Double) {
|
||||
double d = ((Double) value);
|
||||
if (d == Double.POSITIVE_INFINITY) {
|
||||
result.v = "Infinity";
|
||||
} else if (d == Double.NEGATIVE_INFINITY) {
|
||||
result.v = "-Infinity";
|
||||
} else if (d == -0) {
|
||||
result.v = "-0";
|
||||
} else if (Double.isNaN(d)) {
|
||||
result.v = "NaN";
|
||||
} else {
|
||||
result.n = d;
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
result.b = (Boolean) value;
|
||||
} else if (value instanceof Integer) {
|
||||
result.n = (Integer) value;
|
||||
} else if (value instanceof String) {
|
||||
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) {
|
||||
result.r = new SerializedValue.R();
|
||||
result.r.p = ((Pattern)value).pattern();
|
||||
result.r.f = toJsRegexFlags(((Pattern)value));
|
||||
if (value == null) {
|
||||
result.v = "undefined";
|
||||
} else if (value instanceof Double) {
|
||||
double d = ((Double) value);
|
||||
if (d == Double.POSITIVE_INFINITY) {
|
||||
result.v = "Infinity";
|
||||
} else if (d == Double.NEGATIVE_INFINITY) {
|
||||
result.v = "-Infinity";
|
||||
} else if (d == -0) {
|
||||
result.v = "-0";
|
||||
} else if (Double.isNaN(d)) {
|
||||
result.v = "NaN";
|
||||
} else {
|
||||
HashableValue mapKey = new HashableValue(value);
|
||||
Integer id = valueToId.get(mapKey);
|
||||
if (id != null) {
|
||||
result.ref = id;
|
||||
} else {
|
||||
result.id = ++lastId;
|
||||
valueToId.put(mapKey, lastId);
|
||||
if (value instanceof List) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (List<?>) value) {
|
||||
list.add(serializeValue(o));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else if (value instanceof Map) {
|
||||
List<SerializedValue.O> list = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) value;
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
SerializedValue.O o = new SerializedValue.O();
|
||||
o.k = e.getKey();
|
||||
o.v = serializeValue(e.getValue());
|
||||
list.add(o);
|
||||
}
|
||||
result.o = list.toArray(new SerializedValue.O[0]);
|
||||
} else if (value instanceof Object[]) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (Object[]) value) {
|
||||
list.add(serializeValue(o));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else {
|
||||
throw new PlaywrightException("Unsupported type of argument: " + value);
|
||||
}
|
||||
}
|
||||
result.n = d;
|
||||
}
|
||||
return result;
|
||||
} else if (value instanceof Boolean) {
|
||||
result.b = (Boolean) value;
|
||||
} else if (value instanceof Integer) {
|
||||
result.n = (Integer) value;
|
||||
} else if (value instanceof String) {
|
||||
result.s = (String) value;
|
||||
} else if (value instanceof List) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (List<?>) value) {
|
||||
list.add(serializeValue(o, handles, depth + 1));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else if (value instanceof Map) {
|
||||
List<SerializedValue.O> list = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) value;
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
SerializedValue.O o = new SerializedValue.O();
|
||||
o.k = e.getKey();
|
||||
o.v = serializeValue(e.getValue(), handles, depth + 1);
|
||||
list.add(o);
|
||||
}
|
||||
result.o = list.toArray(new SerializedValue.O[0]);
|
||||
} else if (value instanceof Object[]) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (Object[]) value) {
|
||||
list.add(serializeValue(o, handles, depth + 1));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else {
|
||||
throw new PlaywrightException("Unsupported type of argument: " + value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static SerializedArgument serializeArgument(Object arg) {
|
||||
return new ValueSerializer(arg).toSerializedArgument();
|
||||
}
|
||||
|
||||
static <T> T deserialize(SerializedValue value) {
|
||||
return deserialize(value, new HashMap<>());
|
||||
SerializedArgument result = new SerializedArgument();
|
||||
List<JSHandleImpl> handles = new ArrayList<>();
|
||||
result.value = serializeValue(arg, handles, 0);
|
||||
result.handles = new Channel[handles.size()];
|
||||
int i = 0;
|
||||
for (JSHandleImpl handle : handles) {
|
||||
result.handles[i] = new Channel();
|
||||
result.handles[i].guid = handle.guid;
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T deserialize(SerializedValue value, Map<Integer, Object> idToValue) {
|
||||
if (value.ref != null) {
|
||||
return (T) idToValue.get(value.ref);
|
||||
}
|
||||
static <T> T deserialize(SerializedValue value) {
|
||||
if (value.n != null) {
|
||||
if (value.n.doubleValue() == (double) value.n.intValue()) {
|
||||
return (T) Integer.valueOf(value.n.intValue());
|
||||
@@ -223,17 +155,6 @@ class Serialization {
|
||||
return (T) value.b;
|
||||
if (value.s != null)
|
||||
return (T) value.s;
|
||||
if (value.u != null) {
|
||||
try {
|
||||
return (T)(new URL(value.u));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new PlaywrightException("Unexpected value: " + value.u, e);
|
||||
}
|
||||
}
|
||||
if (value.d != null)
|
||||
return (T)(Date.from(Instant.parse(value.d)));
|
||||
if (value.r != null)
|
||||
return (T)(Pattern.compile(value.r.p, fromJsRegexFlags(value.r.f)));
|
||||
if (value.v != null) {
|
||||
switch (value.v) {
|
||||
case "undefined":
|
||||
@@ -254,17 +175,15 @@ class Serialization {
|
||||
}
|
||||
if (value.a != null) {
|
||||
List<Object> list = new ArrayList<>();
|
||||
idToValue.put(value.id, list);
|
||||
for (SerializedValue v : value.a) {
|
||||
list.add(deserialize(v, idToValue));
|
||||
list.add(deserialize(v));
|
||||
}
|
||||
return (T) list;
|
||||
}
|
||||
if (value.o != null) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
idToValue.put(value.id, map);
|
||||
for (SerializedValue.O o : value.o) {
|
||||
map.put(o.k, deserialize(o.v, idToValue));
|
||||
map.put(o.k, deserialize(o.v));
|
||||
}
|
||||
return (T) map;
|
||||
}
|
||||
@@ -291,14 +210,6 @@ class Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
static JsonArray toJsonArray(Path[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (Path p : files) {
|
||||
jsonFiles.add(p.toAbsolutePath().toString());
|
||||
}
|
||||
return jsonFiles;
|
||||
}
|
||||
|
||||
static JsonArray toJsonArray(FilePayload[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (FilePayload p : files) {
|
||||
@@ -318,7 +229,9 @@ class Serialization {
|
||||
static JsonArray toProtocol(ElementHandle[] handles) {
|
||||
JsonArray jsonElements = new JsonArray();
|
||||
for (ElementHandle handle : handles) {
|
||||
jsonElements.add(((ElementHandleImpl) handle).toProtocolRef());
|
||||
JsonObject jsonHandle = new JsonObject();
|
||||
jsonHandle.addProperty("guid", ((ElementHandleImpl) handle).guid);
|
||||
jsonElements.add(jsonHandle);
|
||||
}
|
||||
return jsonElements;
|
||||
}
|
||||
@@ -327,16 +240,6 @@ class Serialization {
|
||||
return toNameValueArray(map);
|
||||
}
|
||||
|
||||
static void addHarUrlFilter(JsonObject options, Object urlFilter) {
|
||||
if (urlFilter instanceof String) {
|
||||
options.addProperty("urlGlob", (String) urlFilter);
|
||||
} else if (urlFilter instanceof Pattern) {
|
||||
Pattern pattern = (Pattern) urlFilter;
|
||||
options.addProperty("urlRegexSource", pattern.pattern());
|
||||
options.addProperty("urlRegexFlags", toJsRegexFlags(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
static JsonArray toNameValueArray(Map<String, ?> map) {
|
||||
JsonArray array = new JsonArray();
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
@@ -348,15 +251,6 @@ class Serialization {
|
||||
return array;
|
||||
}
|
||||
|
||||
static Map<String, String> fromNameValues(JsonArray array) {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
for (JsonElement element : array) {
|
||||
JsonObject pair = element.getAsJsonObject();
|
||||
map.put(pair.get("name").getAsString(), pair.get("value").getAsString());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static List<String> parseStringList(JsonArray array) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (JsonElement e : array) {
|
||||
@@ -387,7 +281,9 @@ class Serialization {
|
||||
private static class HandleSerializer implements JsonSerializer<JSHandleImpl> {
|
||||
@Override
|
||||
public JsonElement serialize(JSHandleImpl src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return src.toProtocolRef();
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", src.guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ 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 {
|
||||
@@ -62,12 +61,6 @@ 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);
|
||||
|
||||
@@ -28,25 +28,18 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class StackTraceCollector {
|
||||
static final String PLAYWRIGHT_JAVA_SRC = "PLAYWRIGHT_JAVA_SRC";
|
||||
private final List<Path> srcDirs;
|
||||
private final Map<Path, String> classToSourceCache = new HashMap<>();
|
||||
|
||||
static StackTraceCollector createFromEnv(Map<String, String> env) {
|
||||
String srcRoots = null;
|
||||
if (env != null) {
|
||||
srcRoots = env.get(PLAYWRIGHT_JAVA_SRC);
|
||||
}
|
||||
if (srcRoots == null) {
|
||||
srcRoots = System.getenv(PLAYWRIGHT_JAVA_SRC);
|
||||
}
|
||||
static StackTraceCollector createFromEnv() {
|
||||
String srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
if (srcRoots == null) {
|
||||
return null;
|
||||
}
|
||||
List<Path> srcDirs = Arrays.stream(srcRoots.split(File.pathSeparator)).map(p -> Paths.get(p)).collect(Collectors.toList());
|
||||
for (Path srcDir: srcDirs) {
|
||||
if (!Files.exists(srcDir.toAbsolutePath())) {
|
||||
throw new PlaywrightException("Source location specified in " + PLAYWRIGHT_JAVA_SRC + " doesn't exist: '" + srcDir.toAbsolutePath() + "'");
|
||||
throw new PlaywrightException("Source location specified in PLAYWRIGHT_JAVA_SRC doesn't exist: '" + srcDir.toAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
return new StackTraceCollector(srcDirs);
|
||||
|
||||
@@ -26,7 +26,9 @@ import java.nio.file.Path;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TracingImpl extends ChannelOwner implements Tracing {
|
||||
LocalUtils localUtils;
|
||||
boolean isRemote;
|
||||
private boolean includeSources;
|
||||
|
||||
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -59,7 +61,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
|
||||
// Add local sources to the remote trace if necessary.
|
||||
if (isRemote && json.has("sourceEntries")) {
|
||||
JsonArray entries = json.getAsJsonArray("sourceEntries");
|
||||
connection.localUtils.zip(path, entries);
|
||||
localUtils.zip(path, entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
|
||||
options = new StartOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
boolean includeSources = options.sources != null && options.sources;
|
||||
includeSources = options.sources != null;
|
||||
if (includeSources) {
|
||||
if (!connection.isCollectingStacks()) {
|
||||
throw new PlaywrightException("Source root directory must be specified via PLAYWRIGHT_JAVA_SRC environment variable when source collection is enabled");
|
||||
|
||||
@@ -97,13 +97,4 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
@@ -25,7 +23,6 @@ import com.microsoft.playwright.options.HttpHeader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
@@ -33,8 +30,6 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
|
||||
|
||||
class Utils {
|
||||
static <F, T> T convertType(F f, Class<T> t) {
|
||||
if (f == null) {
|
||||
@@ -149,56 +144,6 @@ class Utils {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
static final int maxUplodBufferSize = 50 * 1024 * 1024;
|
||||
|
||||
static boolean hasLargeFile(Path[] files) {
|
||||
for (Path file: files) {
|
||||
try {
|
||||
if (Files.size(file)> maxUplodBufferSize) {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Cannot get file size.", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
|
||||
if (context.browser().isRemote) {
|
||||
List<WritableStream> streams = new ArrayList<>();
|
||||
JsonArray jsonStreams = new JsonArray();
|
||||
for (Path path : files) {
|
||||
WritableStream temp = context.createTempFile(path.getFileName().toString());
|
||||
streams.add(temp);
|
||||
try (OutputStream out = temp.stream()) {
|
||||
Files.copy(path, out);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to copy file to remote server.", e);
|
||||
}
|
||||
jsonStreams.add(temp.toProtocolRef());
|
||||
}
|
||||
params.add("streams", jsonStreams);
|
||||
} else {
|
||||
Path[] absolute = Arrays.stream(files).map(f -> {
|
||||
try {
|
||||
return f.toRealPath();
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Cannot get absolute file path", e);
|
||||
}
|
||||
}).toArray(Path[]::new);
|
||||
params.add("localPaths", toJsonArray(absolute));
|
||||
}
|
||||
}
|
||||
|
||||
static void checkFilePayloadSize(FilePayload[] files) {
|
||||
for (FilePayload file: files) {
|
||||
if (file.buffer.length > maxUplodBufferSize) {
|
||||
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FilePayload[] toFilePayloads(Path[] files) {
|
||||
List<FilePayload> payloads = new ArrayList<>();
|
||||
for (Path file : files) {
|
||||
@@ -277,17 +222,6 @@ class Utils {
|
||||
return map;
|
||||
}
|
||||
|
||||
static List<HttpHeader> toHeadersList(Map<String, String> headers) {
|
||||
List<HttpHeader> list = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry: headers.entrySet()) {
|
||||
HttpHeader header = new HttpHeader();
|
||||
header.name = entry.getKey();
|
||||
header.value = entry.getValue();
|
||||
list.add(header);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static String toJsRegexFlags(Pattern pattern) {
|
||||
String regexFlags = "";
|
||||
if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) {
|
||||
@@ -307,18 +241,4 @@ class Utils {
|
||||
}
|
||||
return regexFlags;
|
||||
}
|
||||
|
||||
static int fromJsRegexFlags(String regexFlags) {
|
||||
int flags = 0;
|
||||
if (regexFlags.contains("i")) {
|
||||
flags |= Pattern.CASE_INSENSITIVE;
|
||||
}
|
||||
if (regexFlags.contains("s")) {
|
||||
flags |= Pattern.DOTALL;
|
||||
}
|
||||
if (regexFlags.contains("m")) {
|
||||
flags |= Pattern.MULTILINE;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,6 @@ 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,52 +18,38 @@ 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>, Logger {
|
||||
private final Function<Logger, T> supplier;
|
||||
public class WaitForEventLogger<T> implements Supplier<T> {
|
||||
private final Supplier<T> supplier;
|
||||
private final ChannelOwner channel;
|
||||
private final String waitId;
|
||||
private final String apiName;
|
||||
|
||||
WaitForEventLogger(ChannelOwner channelOwner, String apiName, Function<Logger, T> supplier) {
|
||||
WaitForEventLogger(ChannelOwner channelOwner, String apiName, Supplier<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", "log");
|
||||
info.addProperty("message", message);
|
||||
sendWaitForEventInfo(info);
|
||||
info.addProperty("phase", "after");
|
||||
try {
|
||||
return supplier.get();
|
||||
} catch (RuntimeException e) {
|
||||
info.addProperty("error", e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
sendWaitForEventInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendWaitForEventInfo(JsonObject info) {
|
||||
@@ -71,6 +57,6 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
|
||||
info.addProperty("waitId", waitId);
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("info", info);
|
||||
channel.sendMessageAsync("waitForEventInfo", params);
|
||||
channel.withLogging(apiName, () -> 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", logger -> waitForFrameReceivedImpl(options, code));
|
||||
return withWaitLogging("WebSocket.waitForFrameReceived", () -> 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", logger -> waitForFrameSentImpl(options, code));
|
||||
return withWaitLogging("WebSocket.waitForFrameSent", () -> 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", logger -> waitForCloseImpl(options, code));
|
||||
return withWaitLogging("Worker.waitForClose", () -> waitForCloseImpl(options, code));
|
||||
}
|
||||
|
||||
private Worker waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
class WritableStream extends ChannelOwner {
|
||||
WritableStream(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
OutputStream stream() {
|
||||
return new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
write(new byte[] { (byte) b });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
JsonObject params = new JsonObject();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
ByteBuffer encoded = Base64.getEncoder().encode(buffer);
|
||||
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
|
||||
sendMessage("write", params);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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 HarContentPolicy {
|
||||
OMIT,
|
||||
EMBED,
|
||||
ATTACH
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 HarMode {
|
||||
FULL,
|
||||
MINIMAL
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 HarNotFound {
|
||||
ABORT,
|
||||
FALLBACK
|
||||
}
|
||||
@@ -19,8 +19,7 @@ package com.microsoft.playwright.options;
|
||||
import com.microsoft.playwright.impl.RequestOptionsImpl;
|
||||
|
||||
/**
|
||||
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}. Playwright will automatically
|
||||
* determine content type of the request.
|
||||
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}.
|
||||
* <pre>{@code
|
||||
* context.request().post(
|
||||
* "https://example.com/submit",
|
||||
@@ -28,33 +27,6 @@ import com.microsoft.playwright.impl.RequestOptionsImpl;
|
||||
* .setQueryParam("page", 1)
|
||||
* .setData("My data"));
|
||||
* }</pre>
|
||||
*
|
||||
* <p> **Uploading html form data**
|
||||
*
|
||||
* <p> {@code FormData} class can be used to send a form to the server, by default the request will use
|
||||
* {@code application/x-www-form-urlencoded} encoding:
|
||||
* <pre>{@code
|
||||
* context.request().post("https://example.com/signup", RequestOptions.create().setForm(
|
||||
* FormData.create()
|
||||
* .set("firstName", "John")
|
||||
* .set("lastName", "Doe")));
|
||||
* }</pre>
|
||||
*
|
||||
* <p> You can also send files as fields of an html form. The data will be encoded using <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">{@code multipart/form-data}</a>:
|
||||
* <pre>{@code
|
||||
* Path path = Paths.get("members.csv");
|
||||
* APIResponse response = context.request().post("https://example.com/upload_members",
|
||||
* RequestOptions.create().setMultipart(FormData.create().set("membersList", path)));
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Alternatively, you can build the file payload manually:
|
||||
* <pre>{@code
|
||||
* FilePayload filePayload = new FilePayload("members.csv", "text/csv",
|
||||
* "Alice, 33\nJohn, 35\n".getBytes(StandardCharsets.UTF_8));
|
||||
* APIResponse response = context.request().post("https://example.com/upload_members",
|
||||
* RequestOptions.create().setMultipart(FormData.create().set("membersList", filePayload)));
|
||||
* }</pre>
|
||||
*/
|
||||
public interface RequestOptions {
|
||||
/**
|
||||
@@ -115,13 +87,6 @@ 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>).
|
||||
|
||||
@@ -17,6 +17,5 @@
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum ScreenshotAnimations {
|
||||
DISABLED,
|
||||
ALLOW
|
||||
DISABLED
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 ScreenshotCaret {
|
||||
HIDE,
|
||||
INITIAL
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 ScreenshotScale {
|
||||
CSS,
|
||||
DEVICE
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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 ServiceWorkerPolicy {
|
||||
ALLOW,
|
||||
BLOCK
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MultipartFormData {
|
||||
static MultipartFormData parseRequest(HttpExchange exchange) throws IOException {
|
||||
ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
|
||||
try (OutputStream output = bodyBytes) {
|
||||
Utils.copy(exchange.getRequestBody(), output);
|
||||
}
|
||||
String body = new String(bodyBytes.toByteArray(), StandardCharsets.UTF_8);
|
||||
String contentType = exchange.getRequestHeaders().get("content-type").get(0);
|
||||
Matcher matcher = Pattern.compile("boundary=(.*)$").matcher(contentType);
|
||||
if (!matcher.find()) {
|
||||
throw new RuntimeException("Boundary not found!");
|
||||
}
|
||||
String boundary = matcher.group(1);
|
||||
return new MultipartFormData(body, boundary);
|
||||
}
|
||||
|
||||
static class Field {
|
||||
final String filename;
|
||||
final String content;
|
||||
|
||||
Field(String filename, String content) {
|
||||
this.filename = filename;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
final List<Field> fields = new ArrayList<>();
|
||||
|
||||
MultipartFormData(String body, String boundary) {
|
||||
String[] parts = Pattern.compile("--" + boundary + "(--)?\r\n", Pattern.MULTILINE).split(body);
|
||||
for (String part : parts) {
|
||||
if (part.trim().length() == 0) {
|
||||
continue;
|
||||
}
|
||||
String[] headersAndContent = Pattern.compile("\r\n\r\n", Pattern.MULTILINE).split(part);
|
||||
if (headersAndContent.length != 2) {
|
||||
throw new RuntimeException("Unexpected format: " + part);
|
||||
}
|
||||
String headers = headersAndContent[0];
|
||||
String filename = null;
|
||||
for (String header: Pattern.compile("\r\n", Pattern.MULTILINE).split(headers)) {
|
||||
Matcher matcher = Pattern.compile("content-disposition: .*filename=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE).matcher(header);
|
||||
if (!matcher.find()) {
|
||||
continue;
|
||||
}
|
||||
filename = matcher.group(1);
|
||||
}
|
||||
String content = headersAndContent[1];
|
||||
content = content.substring(0, content.length() - "\r\n".length());
|
||||
fields.add(new Field(filename, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,6 @@ 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,11 +19,8 @@ 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.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestAPIResponseAssertions extends TestBase {
|
||||
@Test
|
||||
@@ -41,62 +38,14 @@ public class TestAPIResponseAssertions extends TestBase {
|
||||
@Test
|
||||
void fail() {
|
||||
APIResponse res = page.request().get(server.PREFIX + "/unknown");
|
||||
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);
|
||||
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());
|
||||
}
|
||||
assertTrue(didThrow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ 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;
|
||||
@@ -37,11 +35,9 @@ 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.
|
||||
@@ -76,12 +72,8 @@ public class TestBase {
|
||||
return options;
|
||||
}
|
||||
|
||||
Playwright.CreateOptions playwrightOptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void initBrowserType() {
|
||||
playwright = Playwright.create(playwrightOptions());
|
||||
playwright = Playwright.create();
|
||||
browserType = Utils.getBrowserTypeFromEnv(playwright);
|
||||
}
|
||||
|
||||
@@ -149,11 +141,4 @@ 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,8 +48,12 @@ public class TestBrowser extends TestBase {
|
||||
@Test
|
||||
void shouldThrowUponSecondCreateNewPage() {
|
||||
Page page = browser.newPage();
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.context().newPage());
|
||||
assertTrue(e.getMessage().contains("Please use browser.newContext()"));
|
||||
try {
|
||||
page.context().newPage();
|
||||
fail("newPage should throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Please use browser.newContext()"));
|
||||
}
|
||||
page.close();
|
||||
}
|
||||
|
||||
@@ -93,9 +97,4 @@ public class TestBrowser extends TestBase {
|
||||
assertNotNull(browser);
|
||||
browser.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnBrowserType() {
|
||||
assertEquals(browserType, browser.browserType());
|
||||
}
|
||||
}
|
||||
|
||||
+27
-13
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.microsoft.playwright.options.Cookie;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -26,6 +27,7 @@ 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.*;
|
||||
|
||||
@@ -49,11 +51,15 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
"}");
|
||||
assertEquals("username=John Doe", documentCookie);
|
||||
List<Cookie> cookies = context.cookies();
|
||||
assertEquals(1, cookies.size());
|
||||
context.clearCookies();
|
||||
assertEquals(0, context.cookies().size());
|
||||
context.addCookies(cookies);
|
||||
assertJsonEquals(cookies, context.cookies());
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -242,19 +248,27 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldNotSetACookieWithBlankPageURL() {
|
||||
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\""));
|
||||
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\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSetACookieOnADataURLPage() {
|
||||
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\""));
|
||||
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\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -116,18 +116,22 @@ public class TestBrowserContextBasic extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldNotAllowDeviceScaleFactorWithNullViewport() {
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0).setViewportSize(null));
|
||||
});
|
||||
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAllowIsMobileWithNullViewport() {
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
browser.newContext(new Browser.NewContextOptions().setIsMobile(true).setViewportSize(null));
|
||||
});
|
||||
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,10 +143,12 @@ public class TestBrowserContextBasic extends TestBase {
|
||||
@Test
|
||||
void closeShouldAbortFutureEvent() {
|
||||
BrowserContext context = browser.newContext();
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
context.waitForPage(() -> context.close());
|
||||
});
|
||||
assertTrue(e.getMessage().contains("Context closed"));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Context closed"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -205,11 +211,15 @@ 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>");
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -234,7 +244,11 @@ public class TestBrowserContextBasic extends TestBase {
|
||||
void shouldWorkWithOfflineOption() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setOffline(true));
|
||||
Page page = context.newPage();
|
||||
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
|
||||
try {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
}
|
||||
|
||||
context.setOffline(false);
|
||||
Response response = page.navigate(server.EMPTY_PAGE);
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -28,7 +29,8 @@ 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.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestBrowserContextCookies extends TestBase {
|
||||
@Test
|
||||
@@ -58,28 +60,25 @@ 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);
|
||||
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.
|
||||
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));
|
||||
int timestamp = (Integer) page.evaluate("+(new Date('1/1/2038'))/1000");
|
||||
Cookie cookie = context.cookies().get(0);
|
||||
assertEquals("username", cookie.name);
|
||||
assertEquals("John Doe", cookie.value);
|
||||
assertEquals("localhost", cookie.domain);
|
||||
assertEquals("/", cookie.path);
|
||||
assertEquals(timestamp, cookie.expires);
|
||||
assertEquals(false, cookie.httpOnly);
|
||||
assertEquals(false, cookie.secure);
|
||||
if (isChromium()) {
|
||||
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
|
||||
} else {
|
||||
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+18
-6
@@ -58,16 +58,28 @@ public class TestBrowserContextExposeFunction extends TestBase {
|
||||
void shouldThrowForDuplicateRegistrations() {
|
||||
context.exposeFunction("foo", args -> null);
|
||||
context.exposeFunction("bar", args -> null);
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.exposeFunction("foo", args -> null));
|
||||
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered"));
|
||||
try {
|
||||
context.exposeFunction("foo", args -> null);
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered"));
|
||||
}
|
||||
|
||||
Page page = context.newPage();
|
||||
e = assertThrows(PlaywrightException.class, () -> page.exposeFunction("foo", args -> null));
|
||||
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered in the browser context"));
|
||||
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"));
|
||||
}
|
||||
|
||||
page.exposeFunction("baz", args -> null);
|
||||
e = assertThrows(PlaywrightException.class, () -> context.exposeFunction("baz", args -> null));
|
||||
assertTrue(e.getMessage().contains("Function \"baz\" has been already registered in one of the pages"));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -51,16 +51,24 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
@Test
|
||||
void shouldThrowOnNetworkError() {
|
||||
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/test"));
|
||||
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/test");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowOnNetworkErrorAfterRedirect() {
|
||||
server.setRedirect("/redirect", "/test");
|
||||
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
|
||||
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/redirect");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -72,8 +80,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
writer.write("<title>A");
|
||||
}
|
||||
});
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/test"));
|
||||
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/test");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,8 +98,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
writer.write("<title>A");
|
||||
}
|
||||
});
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
|
||||
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/redirect");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,10 +134,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
|
||||
@Test
|
||||
void getShouldSupportFailOnStatusCode() {
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/does-not-exist.html", RequestOptions.create().setFailOnStatusCode(true));
|
||||
});
|
||||
assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage());
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -287,27 +305,6 @@ 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 {
|
||||
@@ -388,20 +385,29 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldThrowOnInvalidHeaderValue() {
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
context.request().get(server.EMPTY_PAGE, RequestOptions.create()
|
||||
.setHeader("foo", "недопустимое значение"));
|
||||
});
|
||||
assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage());
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowOnNonHttpSProtocol() {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -411,10 +417,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
exchange.sendResponseHeaders(200, 4096);
|
||||
});
|
||||
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
try {
|
||||
context.request().get(server.PREFIX + "/slow", RequestOptions.create().setTimeout(100));
|
||||
});
|
||||
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -445,8 +453,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
});
|
||||
|
||||
context.setDefaultTimeout(100);
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
|
||||
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -454,8 +466,12 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
|
||||
assertEquals("{\"foo\": \"bar\"}\n", response.text());
|
||||
response.dispose();
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
|
||||
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
|
||||
try {
|
||||
response.body();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -463,9 +479,13 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
|
||||
assertEquals("{\"foo\": \"bar\"}\n", response.text());
|
||||
context.close();
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> response.body());
|
||||
assertTrue(e.getMessage().contains("Response has been disposed") ||
|
||||
try {
|
||||
response.body();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
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 {
|
||||
@@ -594,11 +614,17 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotThrowWhenDataPassedForUnsupportedRequest() {
|
||||
context.request().fetch(server.EMPTY_PAGE, RequestOptions.create()
|
||||
.setMethod("GET").setData("bar"));
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void contextRequestShouldExportSameStorageStateAsContext() {
|
||||
server.setRoute("/setcookie.html", exchange -> {
|
||||
@@ -640,10 +666,18 @@ public class TestBrowserContextFetch extends TestBase {
|
||||
return null;
|
||||
});
|
||||
page.evaluate("() => setTimeout(closeContext, 1000);");
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE));
|
||||
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
|
||||
try {
|
||||
context.request().get(server.EMPTY_PAGE);
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Request context disposed"), 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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
import com.microsoft.playwright.options.HarMode;
|
||||
import com.microsoft.playwright.options.HarNotFound;
|
||||
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.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.Utils.copy;
|
||||
import static com.microsoft.playwright.Utils.extractZip;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static com.microsoft.playwright.options.HarContentPolicy.ATTACH;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestBrowserContextHar extends TestBase {
|
||||
@Test
|
||||
void shouldContextRouteFromHARMatchingTheMethodAndFollowingRedirects() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path);
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
// HAR contains a POST for the css file that should not be used.
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPageRouteFromHARMatchingTheMethodAndFollowingRedirects() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
Page page = context.newPage();
|
||||
page.routeFromHAR(path);
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
// HAR contains a POST for the css file that should not be used.
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallbackContinueShouldContinueWhenNotFoundInHar() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void byDefaultShouldAbortRequestsNotFoundInHar() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path);
|
||||
Page page = context.newPage();
|
||||
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallbackContinueShouldContinueRequestsOnBadHar(@TempDir Path tmpDir) throws IOException {
|
||||
Path path = tmpDir.resolve("test.har");
|
||||
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(path))) {
|
||||
stream.write("{ \"log\" : {} }");
|
||||
}
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlyHandleRequestsMatchingUrlFilter() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK).setUrl("**/*.js"));
|
||||
Page page = context.newPage();
|
||||
context.route("http://no.playwright/", route -> {
|
||||
assertEquals("http://no.playwright/", route.request().url());
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(200)
|
||||
.setContentType("text/html")
|
||||
.setBody("<script src='./script.js'></script><div>hello</div>"));
|
||||
});
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlyContextRouteFromHARRequestsMatchingUrlFilter() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl("**/*.js"));
|
||||
Page page = context.newPage();
|
||||
context.route("http://no.playwright/", route -> {
|
||||
assertEquals("http://no.playwright/", route.request().url());
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(200)
|
||||
.setContentType("text/html")
|
||||
.setBody("<script src='./script.js'></script><div>hello</div>"));
|
||||
});
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlyPageRouteFromHARRequestsMatchingUrlFilter() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
Page page = context.newPage();
|
||||
page.routeFromHAR(path, new Page.RouteFromHAROptions().setUrl("**/*.js"));
|
||||
context.route("http://no.playwright/", route -> {
|
||||
assertEquals("http://no.playwright/", route.request().url());
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(200)
|
||||
.setContentType("text/html")
|
||||
.setBody("<script src='./script.js'></script><div>hello</div>"));
|
||||
});
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportRegexFilter() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*(\\.js|.*\\.css|no.playwright\\/)$")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://no.playwright/");
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void newPageShouldFulfillFromHarMatchingTheMethodAndFollowingRedirects() {
|
||||
Path path = Paths.get("src/test/resources/har-fulfill.har");
|
||||
Page page = browser.newPage();
|
||||
page.routeFromHAR(path);
|
||||
page.navigate("http://no.playwright/");
|
||||
// HAR contains a redirect for the script that should be followed automatically.
|
||||
assertEquals("foo", page.evaluate("window.value"));
|
||||
// HAR contains a POST for the css file that should not be used.
|
||||
assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)");
|
||||
page.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeDocumentURLAfterRedirectedNavigation() {
|
||||
Path path = Paths.get("src/test/resources/har-redirect.har");
|
||||
context.routeFromHAR(path);
|
||||
Page page = context.newPage();
|
||||
Response response = page.waitForNavigation(() -> {
|
||||
page.navigate("https://theverge.com/");
|
||||
page.waitForURL("https://www.theverge.com/");
|
||||
});
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
assertEquals("https://www.theverge.com/", response.request().url());
|
||||
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldChangeDocumentURLAfterRedirectedNavigationOnClick() {
|
||||
Path path = Paths.get("src/test/resources/har-redirect.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<a href='https://theverge.com/'>click me</a>");
|
||||
Response response = page.waitForNavigation(() -> page.click("text=click me"));
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
assertEquals("https://www.theverge.com/", response.request().url());
|
||||
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGoBackToRedirectedNavigation() {
|
||||
Path path = Paths.get("src/test/resources/har-redirect.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://theverge.com/");
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page).hasURL(server.EMPTY_PAGE);
|
||||
Response response = page.goBack();
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
assertEquals("https://www.theverge.com/", response.request().url());
|
||||
assertEquals("https://www.theverge.com/", page.evaluate("location.href"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="isFirefox", disabledReason="Flaky in Firefox, upstream as well")
|
||||
void shouldGoForwardToRedirectedNavigation() {
|
||||
Path path = Paths.get("src/test/resources/har-redirect.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page).hasURL(server.EMPTY_PAGE);
|
||||
page.navigate("https://theverge.com/");
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
page.goBack();
|
||||
assertThat(page).hasURL(server.EMPTY_PAGE);
|
||||
Response response = page.goForward();
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
assertEquals("https://www.theverge.com/", response.request().url());
|
||||
assertEquals("https://www.theverge.com/", page.evaluate("() => location.href"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReloadRedirectedNavigation() {
|
||||
Path path = Paths.get("src/test/resources/har-redirect.har");
|
||||
context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://theverge.com/");
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
Response response = page.reload();
|
||||
assertThat(page).hasURL("https://www.theverge.com/");
|
||||
assertEquals("https://www.theverge.com/", response.request().url());
|
||||
assertEquals("https://www.theverge.com/", page.evaluate("() => location.href"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFulfillFromHarWithContentInAFile() {
|
||||
Path path = Paths.get("src/test/resources/har-sha1.har");
|
||||
context.routeFromHAR(path);
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://no.playwright/");
|
||||
assertEquals("<html><head></head><body>Hello, world</body></html>", page.content());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRoundTripHarZip(@TempDir Path tmpDir) {
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
|
||||
.setRecordHarPath(harPath)
|
||||
.setRecordHarMode(HarMode.MINIMAL))) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProduceExtractedZip(@TempDir Path tmpDir) throws IOException {
|
||||
Path harPath = tmpDir.resolve("har.har");
|
||||
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
|
||||
.setRecordHarPath(harPath)
|
||||
.setRecordHarMode(HarMode.MINIMAL)
|
||||
.setRecordHarContent(ATTACH))) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
assertTrue(Files.exists(harPath));
|
||||
String har = new String(Files.readAllBytes(harPath), StandardCharsets.UTF_8);
|
||||
assertFalse(har.contains("background-color"));
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRoundTripExtractedHarZip(@TempDir Path tmpDir) throws IOException {
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
|
||||
.setRecordHarPath(harPath)
|
||||
.setRecordHarMode(HarMode.MINIMAL))) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
|
||||
Path harDir = tmpDir.resolve("hardir");
|
||||
extractZip(harPath, harDir);
|
||||
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harDir.resolve("har.har"));
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRoundTripHarWithPostData(@TempDir Path tmpDir) {
|
||||
server.setRoute("/echo", exchange -> {
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
try (OutputStream out = exchange.getResponseBody()) {
|
||||
copy(exchange.getRequestBody(), out);
|
||||
}
|
||||
});
|
||||
|
||||
String fetchFunction = "async body => {\n" +
|
||||
" const response = await fetch('/echo', { method: 'POST', body });\n" +
|
||||
" return await response.text();\n" +
|
||||
" }\n";
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
|
||||
.setRecordHarPath(harPath)
|
||||
.setRecordHarMode(HarMode.MINIMAL))) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("1", page1.evaluate(fetchFunction, "1"));
|
||||
assertEquals("2", page1.evaluate(fetchFunction, "2"));
|
||||
assertEquals("3", page1.evaluate(fetchFunction, "3"));
|
||||
}
|
||||
server.reset();
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harPath);
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("1", page2.evaluate(fetchFunction, "1"));
|
||||
assertEquals("2", page2.evaluate(fetchFunction, "2"));
|
||||
assertEquals("3", page2.evaluate(fetchFunction, "3"));
|
||||
assertEquals("3", page2.evaluate(fetchFunction, "3"));
|
||||
assertThrows(PlaywrightException.class, () -> page2.evaluate(fetchFunction, "4"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDisambiguateByHeader(@TempDir Path tmpDir) {
|
||||
server.setRoute("/echo", exchange -> {
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
try (OutputStream out = exchange.getResponseBody()) {
|
||||
List<String> values = exchange.getRequestHeaders().get("baz");
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write(values == null ? "<no header>" : String.join(", ", values));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String fetchFunction = "async bazValue => {\n" +
|
||||
" const response = await fetch('/echo', {\n" +
|
||||
" method: 'POST',\n" +
|
||||
" body: '',\n" +
|
||||
" headers: {\n" +
|
||||
" foo: 'foo-value',\n" +
|
||||
" bar: 'bar-value',\n" +
|
||||
" baz: bazValue,\n" +
|
||||
" }\n" +
|
||||
" });\n" +
|
||||
" return await response.text();\n" +
|
||||
" }\n";
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions()
|
||||
.setRecordHarPath(harPath)
|
||||
.setRecordHarMode(HarMode.MINIMAL))) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("baz1", page1.evaluate(fetchFunction, "baz1"));
|
||||
assertEquals("baz2", page1.evaluate(fetchFunction, "baz2"));
|
||||
assertEquals("baz3", page1.evaluate(fetchFunction, "baz3"));
|
||||
}
|
||||
server.reset();
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harPath);
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("baz1", page2.evaluate(fetchFunction, "baz1"));
|
||||
assertEquals("baz2", page2.evaluate(fetchFunction, "baz2"));
|
||||
assertEquals("baz3", page2.evaluate(fetchFunction, "baz3"));
|
||||
assertEquals("baz1", page2.evaluate(fetchFunction, "baz4"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateHarZipForContext(@TempDir Path tmpDir) {
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext()) {
|
||||
context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true));
|
||||
Page page1 = context1.newPage();
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
|
||||
Page page2 = context2.newPage();
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateHarZipForPage(@TempDir Path tmpDir) {
|
||||
Path harPath = tmpDir.resolve("har.zip");
|
||||
try (BrowserContext context1 = browser.newContext()) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.routeFromHAR(harPath, new Page.RouteFromHAROptions().setUpdate(true));
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
Page page2 = context2.newPage();
|
||||
page2.routeFromHAR(harPath, new Page.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateExtractedHarZipForPage(@TempDir Path tmpDir) {
|
||||
Path harPath = tmpDir.resolve("har.har");
|
||||
try (BrowserContext context1 = browser.newContext()) {
|
||||
Page page1 = context1.newPage();
|
||||
page1.routeFromHAR(harPath, new Page.RouteFromHAROptions().setUpdate(true));
|
||||
page1.navigate(server.PREFIX + "/one-style.html");
|
||||
}
|
||||
try (BrowserContext context2 = browser.newContext()) {
|
||||
Page page2 = context2.newPage();
|
||||
page2.routeFromHAR(harPath, new Page.RouteFromHAROptions().setNotFound(HarNotFound.ABORT));
|
||||
page2.navigate(server.PREFIX + "/one-style.html");
|
||||
assertTrue(page2.content().contains("hello, world!"));
|
||||
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,19 +28,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
public class TestBrowserContextLocale extends TestBase {
|
||||
@Test
|
||||
void shouldAffectAcceptLanguageHeader() throws ExecutionException, InterruptedException {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
|
||||
Page page = context.newPage();
|
||||
Future<Server.Request> request = server.futureRequest("/empty.html");
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("fr-FR", request.get().headers.get("accept-language").get(0).substring(0, 5));
|
||||
assertEquals("fr-CH", request.get().headers.get("accept-language").get(0).substring(0, 5));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAffectNavigatorLanguage() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
|
||||
Page page = context.newPage();
|
||||
assertEquals("fr-FR", page.evaluate("() => navigator.language"));
|
||||
assertEquals("fr-CH", page.evaluate("() => navigator.language"));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class TestBrowserContextLocale extends TestBase {
|
||||
context.close();
|
||||
}
|
||||
{
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals("1 000 000,5", page.evaluate("() => (1000000.50).toLocaleString().replace(/\\s/g, ' ')"));
|
||||
@@ -87,7 +87,7 @@ public class TestBrowserContextLocale extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldFormatNumberInPopups() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Page popup = page.waitForPopup(() -> page.evaluate(
|
||||
@@ -100,14 +100,14 @@ public class TestBrowserContextLocale extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldAffectNavigatorLanguageInPopups() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR"));
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Page popup = page.waitForPopup(() -> page.evaluate(
|
||||
"url => window.open(url)", server.PREFIX + "/formatted-number.html"));
|
||||
popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
|
||||
Object result = popup.evaluate("window.initialNavigatorLanguage");
|
||||
assertEquals("fr-FR", result);
|
||||
assertEquals("fr-CH", result);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class TestBrowserContextLocale extends TestBase {
|
||||
defaultLocale = getContextLocale.apply(context);
|
||||
context.close();
|
||||
}
|
||||
String localeOverride = "es-MX".equals(defaultLocale) ? "de-DE" : "es-MX";
|
||||
String localeOverride = "ru-RU".equals(defaultLocale) ? "de-DE" : "ru-RU";
|
||||
{
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale(localeOverride));
|
||||
assertEquals(localeOverride, getContextLocale.apply(context));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user