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

Compare commits

..

1 Commits

Author SHA1 Message Date
Andrey Lushnikov ea16db9143 chore: mark v1.16.0 (#654) 2021-10-21 08:53:00 -07:00
194 changed files with 1911 additions and 15171 deletions
+2 -4
View File
@@ -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
+2 -3
View File
@@ -1,10 +1,9 @@
name: Publish
on:
release:
types: [published]
workflow_dispatch:
push:
branches:
- main
- master
jobs:
build:
timeout-minutes: 30
+8 -7
View File
@@ -3,7 +3,7 @@ name: "devrelease:docker"
on:
push:
branches:
- main
- master
jobs:
publish-canary-docker:
name: "publish to DockerHub"
@@ -17,9 +17,10 @@ jobs:
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/checkout@v2
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: publish docker canary
run: ./utils/docker/publish_docker.sh canary
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: tag & publish
run: |
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next-focal
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:sha-${{ github.sha }}
+16 -13
View File
@@ -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:
@@ -22,12 +17,20 @@ jobs:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- uses: actions/checkout@v2
- run: ./utils/docker/publish_docker.sh stable
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
- run: ./utils/docker/publish_docker.sh canary
if: (github.event_name != 'workflow_dispatch' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release != 'true')
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: tag & publish
run: |
# GITHUB_REF has a form of `refs/tags/v1.3.0`.
# TAG_NAME would be `v1.3.0`
TAG_NAME=${GITHUB_REF#refs/tags/}
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]];
then
echo "Wrong TAG_NAME format: $TAG_NAME"
exit 1
fi
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:latest
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:focal
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}-focal
+27 -54
View File
@@ -2,11 +2,11 @@ name: Build & Test
on:
push:
branches:
- main
- master
- release-*
pull_request:
branches:
- main
- master
- release-*
jobs:
dev:
@@ -19,31 +19,32 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: microsoft/playwright-github-action@v1.5.0
- name: Set up JDK 1.8
uses: actions/setup-java@v2
uses: actions/setup-java@v1
with:
distribution: zulu
java-version: 8
java-version: 1.8
- 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
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test --no-transfer-progress
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
env:
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_JAVA_SRC: src/test/java
- name: Test Spring Boot Starter
shell: bash
env:
BROWSER: ${{ matrix.browser }}
run: |
mvn -B install -D skipTests --no-transfer-progress
cd tools/test-spring-boot-starter
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot*.jar
@@ -63,56 +64,28 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: microsoft/playwright-github-action@v1.5.0
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v2
uses: actions/setup-java@v1
with:
distribution: zulu
java-version: 8
java-version: 1.8
- 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
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test --no-transfer-progress
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
Java_17:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: adopt
java-version: 17
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
BROWSER: ${{ matrix.browser }}
- name: Test Spring Boot Starter
shell: bash
env:
BROWSER: ${{ matrix.browser }}
run: |
cd tools/test-spring-boot-starter
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot*.jar
+4 -5
View File
@@ -2,15 +2,17 @@ name: Test CLI
on:
push:
branches:
- main
- master
- release-*
pull_request:
branches:
- main
- master
- release-*
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -26,6 +28,3 @@ jobs:
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
- name: Test CLI version
shell: bash
run: tools/test-cli-version/test.sh
+5 -5
View File
@@ -3,18 +3,18 @@ on:
push:
paths:
- '.github/workflows/test_docker.yml'
- '**/Dockerfile*'
- 'Dockerfile*'
branches:
- main
- master
- release-*
pull_request:
paths:
- .github/workflows/test_docker.yml
- '**/Dockerfile*'
- Dockerfile.*
- scripts/CLI_VERSION
- '**/pom.xml'
branches:
- main
- master
- release-*
jobs:
test:
@@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: bash utils/docker/build.sh --amd64 focal playwright-java:localbuild-focal
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: Test
run: |
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-focal /bin/bash)"
@@ -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 }}
+11 -3
View File
@@ -2,14 +2,14 @@ name: Verify API
on:
push:
branches:
- main
- master
- release-*
paths:
- 'scripts/*'
- 'api-generator/*'
pull_request:
branches:
- main
- master
- release-*
paths:
- 'scripts/**'
@@ -17,10 +17,18 @@ on:
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
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
View File
@@ -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)
+32
View File
@@ -0,0 +1,32 @@
FROM ubuntu:focal
# === INSTALL JDK and Maven ===
RUN apt-get update && apt-get install -y --no-install-recommends \
openjdk-11-jdk maven
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
# Install utilities required for downloading driver
RUN apt-get update && apt-get install -y --no-install-recommends \
curl unzip
# === INSTALL playwright maven modules & browsers ===
# Browsers will remain downloaded in `/ms-playwright`.
# Note: make sure to set 777 to the registry so that any user can access
# registry.
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN mkdir /ms-playwright && chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
RUN mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && \
./scripts/download_driver_for_all_platforms.sh && \
mvn install -D skipTests --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java
+6 -6
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->104.0.5112.48<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->102.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->97.0.4666.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->92.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
@@ -43,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.17.0</version>
<version>1.14.1</version>
</dependency>
```
@@ -173,13 +173,13 @@ 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).
## Contributing
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
## Is Playwright for Java ready?
+17 -2
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.24.1</version>
<version>1.16.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
<name>Playwright - Drivers For All Platforms</name>
<description>
This module includes Playwright driver and related utilities for all supported platforms.
This module includes playwright-cli binary and related utilities for all supported platforms.
It is intended to be used on the systems where Playwright driver is not preinstalled.
</description>
@@ -28,10 +28,25 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
@@ -28,24 +28,15 @@ public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private final Path driverTempDir;
public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
driverTempDir = alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
DriverJar() throws IOException, URISyntaxException, InterruptedException {
driverTempDir = Files.createTempDirectory("playwright-java-");
driverTempDir.toFile().deleteOnExit();
logMessage("created DriverJar: " + driverTempDir);
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
protected void initialize(Map<String, String> env) throws Exception {
extractDriverToTempDir();
logMessage("extracted driver from jar to " + driverPath());
if (installBrowsers)
installBrowsers(env);
installBrowsers(env);
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
@@ -57,13 +48,13 @@ public class DriverJar extends Driver {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
Path driver = driverPath();
String cliFileName = super.cliFileName();
Path driver = driverTempDir.resolve(cliFileName);
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
}
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();
@@ -139,17 +130,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";
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
}
if (name.contains("linux")) {
if (arch.equals("aarch64")) {
return "linux-arm64";
} else {
return "linux";
}
return "linux";
}
if (name.contains("mac os x")) {
return "mac";
@@ -17,79 +17,22 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.DriverJar;
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.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 org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
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");
// 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");
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
for (int i = 0; i < 2; i++){
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
String message = exception.getMessage();
assertTrue(message.contains("Failed to create driver"), message);
}
field.set(Driver.class, value);
}
@Test
void playwrightCliInstalled() throws Exception {
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap());
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
@@ -99,34 +42,4 @@ public class TestInstall {
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.ensureDriverInstalled(Collections.emptyMap(), false));
}
@Test
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
// Reset instance field value to null for the test.
Field field = Driver.class.getDeclaredField("instance");
field.setAccessible(true);
Object value = field.get(Driver.class);
field.set(Driver.class, null);
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
assertEquals("Failed to create driver", thrown.getMessage());
field.set(Driver.class, value);
}
}
+17 -2
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.24.1</version>
<version>1.16.0</version>
</parent>
<artifactId>driver</artifactId>
<name>Playwright - Driver</name>
<description>
This module provides API for discovery and launching of Playwright driver.
This module provides API for discovery and launching of playwright-cli binary.
</description>
<build>
@@ -24,10 +24,25 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
@@ -18,11 +18,8 @@ package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Map;
import static com.microsoft.playwright.impl.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
@@ -34,12 +31,11 @@ public abstract class Driver {
private static class PreinstalledDriver extends Driver {
private final Path driverDir;
PreinstalledDriver(Path driverDir) {
logMessage("created PreinstalledDriver: " + driverDir);
this.driverDir = driverDir;
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
protected void initialize(Map<String, String> env) {
// no-op
}
@@ -49,48 +45,24 @@ public abstract class Driver {
}
}
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
if (instance == null) {
try {
instance = createDriver();
logMessage("initializing driver");
instance.initialize(env, installBrowsers);
logMessage("driver initialized.");
instance.initialize(env);
} catch (Exception exception) {
instance = null;
throw new RuntimeException("Failed to create driver", exception);
}
}
return instance.driverPath();
String name = instance.cliFileName();
return instance.driverDir().resolve(name);
}
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
protected abstract void initialize(Map<String, String> env) throws Exception;
public Path driverPath() {
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
protected String cliFileName() {
return System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright.cmd" : "playwright.sh";
return driverDir().resolve(cliFileName);
}
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);
}
}
private static String getMajorJavaVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
return version.substring(2, 3);
}
int dot = version.indexOf(".");
if (dot != -1) {
return version.substring(0, dot);
}
return version;
}
private static Driver createDriver() throws Exception {
@@ -99,16 +71,9 @@ public abstract class Driver {
return new PreinstalledDriver(Paths.get(pathFromProperty));
}
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:install " + message);
}
}
@@ -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;
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
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.24.1</version>
<version>1.16.0</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.15.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")));
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"catalogs": {},
"aliases": {
"playwright": {
"script-ref": "scripts/playwright.java",
"description": "Playwright lets you automate Chromium, Firefox and Webkit with a single API. \nWith this cli you can install, trace, generate pdf and screenshots and more.\nExample on how to record and run a script:\n```\n jbang playwright@microsoft/playwright-java codegen -o Example.java`\n jbang --deps com.microsoft.playwright:playwright:RELEASE Example.java\n```"
}
},
"templates": {}
}
+11 -22
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.24.1</version>
<version>1.16.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -31,7 +31,17 @@
<configuration>
<subpackages>com.microsoft.playwright</subpackages>
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
<additionalOptions>--allow-script-in-comments</additionalOptions>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -41,24 +51,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<targetPath>resources</targetPath>
</testResource>
</testResources>
</build>
<dependencies>
<dependency>
@@ -73,10 +66,6 @@
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver</artifactId>
@@ -1,184 +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.*;
import java.nio.file.Path;
import java.util.*;
/**
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance which
* in turn can be used for sending web requests. An instance of this class can be obtained via {@link Playwright#request
* Playwright.request()}. For more information see {@code APIRequestContext}.
*/
public interface APIRequest {
class NewContextOptions {
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Network proxy settings.
*/
public Proxy proxy;
/**
* 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()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public String storageState;
/**
* 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()}. Path to the file with saved storage
* state.
*/
public Path storageStatePath;
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public Double timeout;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public NewContextOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
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()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public NewContextOptions setStorageState(String storageState) {
this.storageState = storageState;
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()}. Path to the file with saved storage
* state.
*/
public NewContextOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public NewContextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* Specific user agent to use in this context.
*/
public NewContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
}
/**
* Creates new instances of {@code APIRequestContext}.
*/
default APIRequestContext newContext() {
return newContext(null);
}
/**
* Creates new instances of {@code APIRequestContext}.
*/
APIRequestContext newContext(NewContextOptions options);
}
@@ -1,227 +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.*;
import java.nio.file.Path;
/**
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
* environment or the service to your e2e test.
*
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage with the
* browser context and can be accessed via {@link BrowserContext#request BrowserContext.request()} or {@link Page#request
* Page.request()}. It is also possible to create a new APIRequestContext instance manually by calling {@link
* APIRequest#newContext APIRequest.newContext()}.
*
* <p> **Cookie management**
*
* <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 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 {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public Path path;
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public StorageStateOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> 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.
*
* @param url Target URL.
*/
default APIResponse delete(String url) {
return delete(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse delete(String url, RequestOptions params);
/**
* All responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar methods are stored in the
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}. This method discards all stored
* responses, and makes {@link APIResponse#body APIResponse.body()} throw "Response disposed" error.
*/
void dispose();
/**
* 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.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
*/
default APIResponse fetch(String urlOrRequest) {
return fetch(urlOrRequest, null);
}
/**
* 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.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
*/
APIResponse fetch(String urlOrRequest, RequestOptions params);
/**
* 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.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
*/
default APIResponse fetch(Request urlOrRequest) {
return fetch(urlOrRequest, null);
}
/**
* 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.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
*/
APIResponse fetch(Request urlOrRequest, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> 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.
*
* @param url Target URL.
*/
default APIResponse get(String url) {
return get(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse get(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> 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.
*
* @param url Target URL.
*/
default APIResponse head(String url) {
return head(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse head(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> 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.
*
* @param url Target URL.
*/
default APIResponse patch(String url) {
return patch(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse patch(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> 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.
*
* @param url Target URL.
*/
default APIResponse post(String url) {
return post(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse post(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> 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.
*
* @param url Target URL.
*/
default APIResponse put(String url) {
return put(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> 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.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse put(String url, RequestOptions params);
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*/
default String storageState() {
return storageState(null);
}
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*/
String storageState(StorageStateOptions options);
}
@@ -1,65 +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.*;
import java.util.*;
/**
* {@code APIResponse} class represents responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar
* methods.
*/
public interface APIResponse {
/**
* Returns the buffer with response body.
*/
byte[] body();
/**
* Disposes the body of this response. If not called then the body will stay in memory until the context closes.
*/
void dispose();
/**
* An object with all the response HTTP headers associated with this response.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*/
boolean ok();
/**
* Contains the status code of the response (e.g., 200 for a success).
*/
int status();
/**
* Contains the status text of the response (e.g. usually an "OK" for a success).
*/
String statusText();
/**
* Returns the text representation of response body.
*/
String text();
/**
* Contains the URL of the response.
*/
String url();
}
@@ -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
@@ -58,7 +57,7 @@ public interface Browser extends AutoCloseable {
class NewContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
@@ -70,8 +69,6 @@ public interface Browser extends AutoCloseable {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
@@ -144,17 +141,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 persistet 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}.
*/
@@ -165,7 +151,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.
@@ -187,15 +172,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()}.
@@ -208,7 +184,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.
*/
@@ -229,7 +205,7 @@ public interface Browser extends AutoCloseable {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -244,8 +220,6 @@ public interface Browser extends AutoCloseable {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public NewContextOptions setBaseURL(String baseURL) {
@@ -385,23 +359,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 persistet 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}.
*/
@@ -418,14 +375,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.
@@ -474,18 +423,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()}.
@@ -504,7 +441,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.
*/
@@ -544,7 +481,7 @@ public interface Browser extends AutoCloseable {
}
class NewPageOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
@@ -556,8 +493,6 @@ public interface Browser extends AutoCloseable {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
@@ -630,17 +565,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 persistet 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}.
*/
@@ -651,7 +575,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.
@@ -673,15 +596,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()}.
@@ -694,7 +608,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.
*/
@@ -715,7 +629,7 @@ public interface Browser extends AutoCloseable {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -730,8 +644,6 @@ public interface Browser extends AutoCloseable {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public NewPageOptions setBaseURL(String baseURL) {
@@ -871,23 +783,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 persistet 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}.
*/
@@ -904,14 +799,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.
@@ -960,18 +847,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()}.
@@ -990,7 +865,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.
*/
@@ -1064,10 +939,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).
@@ -1075,10 +946,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();
@@ -1098,11 +965,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 explicilty 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.
@@ -1110,10 +972,6 @@ public interface Browser extends AutoCloseable {
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Gracefull close up everything
* context.close();
* browser.close();
* }</pre>
*/
default BrowserContext newContext() {
@@ -1121,11 +979,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 explicilty 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.
@@ -1133,10 +986,6 @@ public interface Browser extends AutoCloseable {
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
*
* // Gracefull close up everything
* context.close();
* browser.close();
* }</pre>
*/
BrowserContext newContext(NewContextOptions options);
@@ -1160,9 +1009,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.
@@ -1180,9 +1028,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.
@@ -1198,9 +1045,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.
@@ -1216,9 +1062,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.locator("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.locator("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.locator("button").click();
* page.click("button");
* }
* }
* }
@@ -553,6 +497,7 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
@@ -579,6 +524,7 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
@@ -601,17 +547,13 @@ public interface BrowserContext extends AutoCloseable {
* Returns all open pages in the context.
*/
List<Page> pages();
/**
* API testing helper associated with this context. Requests made with this API will use context cookies.
*/
APIRequestContext request();
/**
* 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 +603,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 +653,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 +705,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 +755,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 +807,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 +853,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;
@@ -129,7 +130,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
@@ -221,7 +222,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel;
@@ -230,7 +231,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(String channel) {
this.channel = channel;
@@ -369,7 +370,7 @@ public interface BrowserType {
}
class LaunchPersistentContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
@@ -386,8 +387,6 @@ public interface BrowserType {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
@@ -398,7 +397,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
@@ -516,17 +515,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 persistet 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}.
*/
@@ -537,7 +525,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.
@@ -559,21 +546,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.
*/
@@ -603,7 +581,7 @@ public interface BrowserType {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -626,8 +604,6 @@ public interface BrowserType {
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public LaunchPersistentContextOptions setBaseURL(String baseURL) {
@@ -645,7 +621,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel;
@@ -654,7 +630,7 @@ public interface BrowserType {
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel;
@@ -865,23 +841,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 persistet 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}.
*/
@@ -898,14 +857,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.
@@ -954,18 +905,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.
*/
@@ -974,7 +913,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.
*/
@@ -1028,7 +967,7 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance.
* This methods attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -1036,13 +975,13 @@ public interface BrowserType {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance.
* 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()}.
*
@@ -1055,7 +994,7 @@ 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()}.
*
@@ -29,13 +29,11 @@ import static java.util.Arrays.asList;
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
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);
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
pb.environment().put("PW_CLI_TARGET_LANG", "java");
}
pb.inheritIO();
Process process = pb.start();
@@ -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 {
/**
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
/**
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
@@ -18,6 +18,7 @@ package com.microsoft.playwright;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.*;
/**
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
@@ -27,18 +28,22 @@ import java.nio.file.Path;
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.locator("a").click());
* 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.locator("a").click();
* page.click("a");
* });
* // wait for download to complete
* Path path = download.path();
* }</pre>
*
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the downloaded
* content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not performed and user
* has no access to the downloaded files.
*/
public interface Download {
/**
File diff suppressed because it is too large Load Diff
@@ -18,11 +18,12 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
@@ -74,50 +75,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
@@ -1,132 +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 java.util.regex.Pattern;
/**
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
* locator.click();
* }</pre>
*
* <p> **Strictness**
*
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
* given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* 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().locator("button").click();
* }</pre>
*
* <p> **Converting Locator to FrameLocator**
*
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/:scope">{@code :scope}</a> CSS selector:
* <pre>{@code
* Locator frameLocator = locator.frameLocator(':scope');
* }</pre>
*/
public interface FrameLocator {
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
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>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
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>}.
*/
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>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
/**
* Returns locator to the first matching frame.
*/
FrameLocator first();
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe.
*
* @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.
*/
FrameLocator frameLocator(String selector);
/**
* Returns locator to the last matching frame.
*/
FrameLocator last();
/**
* 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.
*/
default Locator locator(String selector) {
return locator(selector, null);
}
/**
* 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.
*/
FrameLocator nth(int index);
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
File diff suppressed because it is too large Load Diff
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
@@ -122,12 +123,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
@@ -64,13 +64,9 @@ public interface Playwright extends AutoCloseable {
* This object can be used to launch or connect to Firefox, returning instances of {@code Browser}.
*/
BrowserType firefox();
/**
* Exposes API that can be used for the Web API testing.
*/
APIRequest request();
/**
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
*/
Selectors selectors();
/**
@@ -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 fullfilled 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();
/**
@@ -16,14 +16,13 @@
package com.microsoft.playwright;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
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 +79,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.
@@ -158,11 +101,6 @@ public interface Route {
* is resolved relative to the current working directory.
*/
public Path path;
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public APIResponse response;
/**
* Response status code, defaults to {@code 200}.
*/
@@ -204,14 +142,6 @@ public interface Route {
this.path = path;
return this;
}
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public FulfillOptions setResponse(APIResponse response) {
this.response = response;
return this;
}
/**
* Response status code, defaults to {@code 200}.
*/
@@ -256,8 +186,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 +201,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.
*
@@ -17,10 +17,11 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
*/
public interface Selectors {
class RegisterOptions {
@@ -61,11 +62,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* ElementHandle button = page.querySelector("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();
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
@@ -96,11 +97,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* ElementHandle button = page.querySelector("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();
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
@@ -129,11 +130,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* ElementHandle button = page.querySelector("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();
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
@@ -164,11 +165,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* ElementHandle button = page.querySelector("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();
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
/**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
@@ -17,10 +17,11 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
* href="https://playwright.dev/java/docs/trace-viewer">Trace Viewer</a> after Playwright script runs.
* href="https://playwright.dev/java/docs/trace-viewer/">Trace Viewer</a> after Playwright script runs.
*
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code
@@ -47,23 +48,9 @@ public interface Tracing {
*/
public Boolean screenshots;
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
* Whether to capture DOM snapshot on every action.
*/
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).
*/
public Boolean sources;
/**
* Trace name to be shown in the Trace Viewer.
*/
public String title;
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder specified
@@ -81,46 +68,12 @@ public interface Tracing {
return this;
}
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
* Whether to capture DOM snapshot on every action.
*/
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
}
/**
* 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).
*/
public StartOptions setSources(boolean sources) {
this.sources = sources;
return this;
}
/**
* Trace name to be shown in the Trace Viewer.
*/
public StartOptions setTitle(String title) {
this.title = title;
return this;
}
}
class StartChunkOptions {
/**
* Trace name to be shown in the Trace Viewer.
*/
public String title;
/**
* Trace name to be shown in the Trace Viewer.
*/
public StartChunkOptions setTitle(String title) {
this.title = title;
return this;
}
}
class StopOptions {
/**
@@ -192,7 +145,7 @@ public interface Tracing {
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=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")));
@@ -204,34 +157,7 @@ public interface Tracing {
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
default void startChunk() {
startChunk(null);
}
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.locator("text=Get Started").click();
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
void startChunk(StartChunkOptions options);
void startChunk();
/**
* Stop tracing.
*/
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
/**
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
@@ -1,56 +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.assertions;
/**
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code APIResponse}
* in the tests. A new instance of {@code APIResponseAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* @Test
* void navigatesToLoginPage() {
* ...
* APIResponse response = page.request().get('https://playwright.dev');
* assertThat(response).isOK();
* }
* }
* }</pre>
*/
public interface APIResponseAssertions {
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
* successful:
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
*/
APIResponseAssertions not();
/**
* Ensures the response status code is within [200..299] range.
* <pre>{@code
* assertThat(response).isOK();
* }</pre>
*/
void isOK();
}
@@ -1,158 +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.assertions;
import java.util.regex.Pattern;
/**
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
* tests. A new instance of {@code PageAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* @Test
* void navigatesToLoginPage() {
* ...
* page.locator("#login").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
* }</pre>
*/
public interface PageAssertions {
class HasTitleOptions {
/**
* Time to retry the assertion for.
*/
public Double timeout;
/**
* Time to retry the assertion for.
*/
public HasTitleOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasURLOptions {
/**
* Time to retry the assertion for.
*/
public Double timeout;
/**
* Time to retry the assertion for.
*/
public HasURLOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
* {@code "error"}:
* <pre>{@code
* assertThat(page).not().hasURL("error");
* }</pre>
*/
PageAssertions not();
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
default void hasTitle(String titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
void hasTitle(String titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
default void hasTitle(Pattern titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
void hasTitle(Pattern titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
*/
default void hasURL(String urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
*/
void hasURL(String urlOrRegExp, HasURLOptions options);
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
*/
default void hasURL(Pattern urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
*/
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
}
@@ -1,90 +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.assertions;
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.LocatorAssertionsImpl;
import com.microsoft.playwright.impl.PageAssertionsImpl;
/**
* Playwright gives you Web-First Assertions with convenience methods for creating assertions that will wait and retry
* until the expected condition is met.
*
* <p> Consider the following example:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestExample {
* ...
* @Test
* void statusBecomesSubmitted() {
* ...
* page.locator("#submit-button").click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
* }</pre>
*
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"} text. It
* will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is reached.
* You can pass this timeout as an option.
*
* <p> By default, the timeout for assertions is set to 5 seconds.
*/
public interface PlaywrightAssertions {
/**
* Creates a {@code APIResponseAssertions} object for the given {@code APIResponse}.
* <pre>{@code
* PlaywrightAssertions.assertThat(response).isOK();
* }</pre>
*
* @param response {@code APIResponse} object to use for assertions.
*/
static APIResponseAssertions assertThat(APIResponse response) {
return new APIResponseAssertionsImpl(response);
}
/**
* Creates a {@code LocatorAssertions} object for the given {@code Locator}.
* <pre>{@code
* PlaywrightAssertions.assertThat(locator).isVisible();
* }</pre>
*
* @param locator {@code Locator} object to use for assertions.
*/
static LocatorAssertions assertThat(Locator locator) {
return new LocatorAssertionsImpl(locator);
}
/**
* Creates a {@code PageAssertions} object for the given {@code Page}.
* <pre>{@code
* PlaywrightAssertions.assertThat(page).hasTitle("News");
* }</pre>
*
* @param page {@code Page} object to use for assertions.
*/
static PageAssertions assertThat(Page page) {
return new PageAssertionsImpl(page);
}
}
@@ -1,199 +0,0 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.RequestOptions;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
}
@Override
public APIResponse delete(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "DELETE"));
}
@Override
public void dispose() {
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
}
@Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
}
@Override
public APIResponse fetch(Request request, RequestOptions optionsArg) {
RequestOptionsImpl options = (RequestOptionsImpl) optionsArg;
if (options == null) {
options = new RequestOptionsImpl();
}
if (options.method == null) {
options.method = request.method();
}
if (options.headers == null) {
options.headers = request.headers();
}
if (options.data == null && options.form == null && options.multipart == null) {
options.data = request.postDataBuffer();
}
return fetch(request.url(), options);
}
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
if (options == null) {
options = new RequestOptionsImpl();
}
JsonObject params = new JsonObject();
params.addProperty("url", url);
if (options.params != null) {
Map<String, String> queryParams = new LinkedHashMap<>();
for (Map.Entry<String, ?> e : options.params.entrySet()) {
queryParams.put(e.getKey(), "" + e.getValue());
}
params.add("params", toNameValueArray(queryParams));
}
if (options.method != null) {
params.addProperty("method", options.method);
}
if (options.headers != null) {
params.add("headers", toProtocol(options.headers));
}
if (options.data != null) {
byte[] bytes = null;
if (options.data instanceof byte[]) {
bytes = (byte[]) options.data;
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
}
if (bytes == null) {
params.add("jsonData", gson().toJsonTree(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
}
if (options.form != null) {
params.add("formData", toNameValueArray(options.form.fields));
}
if (options.multipart != null) {
params.add("multipartData", serializeMultipartData(options.multipart.fields));
}
if (options.timeout != null) {
params.addProperty("timeout", options.timeout);
}
if (options.failOnStatusCode != null) {
params.addProperty("failOnStatusCode", options.failOnStatusCode);
}
if (options.ignoreHTTPSErrors != null) {
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
}
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
private static boolean isJsonContentType(Map<String, String> headers) {
if (headers == null) {
return false;
}
for (Map.Entry<String, String> e : headers.entrySet()) {
if ("content-type".equalsIgnoreCase(e.getKey())) {
return "application/json".equals(e.getValue());
}
}
return false;
}
private static JsonArray serializeMultipartData(Map<String, Object> data) {
JsonArray result = new JsonArray();
for (Map.Entry<String, Object> e : data.entrySet()) {
FilePayload filePayload = null;
if (e.getValue() instanceof FilePayload) {
filePayload = (FilePayload) e.getValue();
} else if (e.getValue() instanceof Path) {
filePayload = toFilePayload((Path) e.getValue());
} else if (e.getValue() instanceof File) {
filePayload = toFilePayload(((File) e.getValue()).toPath());
}
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
if (filePayload == null) {
item.addProperty("value", "" + e.getValue());
} else {
item.add("file", toProtocol(filePayload));
}
result.add(item);
}
return result;
}
@Override
public APIResponse get(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "GET"));
}
@Override
public APIResponse head(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "HEAD"));
}
@Override
public APIResponse patch(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PATCH"));
}
@Override
public APIResponse post(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "POST"));
}
@Override
public APIResponse put(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PUT"));
}
@Override
public String storageState(StorageStateOptions options) {
return withLogging("APIRequestContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
String storageState = json.toString();
if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
});
}
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
RequestOptionsImpl impl = (RequestOptionsImpl) options;
if (impl == null) {
impl = new RequestOptionsImpl();
}
if (impl.method == null) {
impl.method = method;
}
return impl;
}
}
@@ -1,53 +0,0 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import static com.microsoft.playwright.impl.Serialization.gson;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
APIRequestImpl(PlaywrightImpl playwright) {
this.playwright = playwright;
}
@Override
public APIRequestContextImpl newContext(NewContextOptions options) {
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
}
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
}
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
return context;
}
}
@@ -1,59 +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.APIResponse;
import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
private final boolean isNot;
APIResponseAssertionsImpl(APIResponse response, boolean isNot) {
this.actual = response;
this.isNot = isNot;
}
public APIResponseAssertionsImpl(APIResponse response) {
this(response, false);
}
@Override
public APIResponseAssertions not() {
return new APIResponseAssertionsImpl(actual, !isNot);
}
@Override
public void isOK() {
if (actual.ok() == !isNot) {
return;
}
String message = "Response status expected to be within [200..299] range, was " + actual.status();
if (isNot) {
message = message.replace("expected to", "expected not to");
}
List<String> logList = ((APIResponseImpl) actual).fetchLog();
String log = String.join("\n", logList);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
throw new AssertionFailedError(message + log);
}
}
@@ -1,124 +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.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HttpHeader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
class APIResponseImpl implements APIResponse {
final APIRequestContextImpl context;
private final JsonObject initializer;
private final RawHeaders headers;
APIResponseImpl(APIRequestContextImpl apiRequestContext, JsonObject response) {
context = apiRequestContext;
initializer = response;
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
}
@Override
public byte[] body() {
return context.withLogging("APIResponse.body", () -> {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
}
return Base64.getDecoder().decode(json.get("binary").getAsString());
} catch (PlaywrightException e) {
if (isSafeCloseError(e)) {
throw new PlaywrightException("Response has been disposed");
}
throw e;
}
});
}
@Override
public void dispose() {
context.withLogging("APIResponse.dispose", () -> {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params);
});
}
@Override
public Map<String, String> headers() {
return headers.headers();
}
@Override
public List<HttpHeader> headersArray() {
return headers.headersArray();
}
@Override
public boolean ok() {
int status = status();
return status == 0 || (status >= 200 && status <= 299);
}
@Override
public int status() {
return initializer.get("status").getAsInt();
}
@Override
public String statusText() {
return initializer.get("statusText").getAsString();
}
@Override
public String text() {
return new String(body(), StandardCharsets.UTF_8);
}
@Override
public String url() {
return initializer.get("url").getAsString();
}
String fetchUid() {
return initializer.get("fetchUid").getAsString();
}
List<String> fetchLog() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
}
}
@@ -1,94 +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;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList;
class AssertionsBase {
final LocatorImpl actualLocator;
final boolean isNot;
AssertionsBase(LocatorImpl actual, boolean isNot) {
this.actualLocator = actual;
this.isNot = isNot;
}
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
expectImpl(expression, asList(textValue), expected, message, options);
}
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
options.isNot = isNot;
expectImpl(expression, options, expected, message);
}
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = 5_000.0;
}
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = String.join("\n", result.log);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
if (expected == null) {
throw new AssertionFailedError(message + log);
}
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
private static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value);
}
Collection<String> values = asList((Object[]) value).stream().map(e -> e.toString()).collect(Collectors.toList());
String stringRepresentation = "[" + String.join(", ", values) + "]";
return ValueWrapper.create(value, stringRepresentation);
}
static ExpectedTextValue expectedRegex(Pattern pattern) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.regexSource = pattern.pattern();
if (pattern.flags() != 0) {
expected.regexFlags = toJsRegexFlags(pattern);
}
return expected;
}
}
@@ -20,20 +20,24 @@ 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
@@ -43,7 +47,6 @@ import static java.util.Arrays.asList;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
@@ -53,17 +56,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,
@@ -81,15 +74,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
}
void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
}
this.tracing = new TracingImpl(this);
}
void setBaseUrl(String spec) {
@@ -187,7 +172,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
return cookies(url == null ? new ArrayList<>() : asList(url));
}
private void closeImpl() {
@@ -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();
}
@@ -360,14 +329,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return new ArrayList<>(pages);
}
@Override
public APIRequestContextImpl request() {
return request;
}
@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 +344,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 +355,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", () -> {
@@ -491,7 +424,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public TracingImpl tracing() {
public Tracing tracing() {
return tracing;
}
@@ -524,28 +457,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
maybeDisableNetworkInterception();
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
maybeDisableNetworkInterception();
}
if (!route.isHandled()){
route.resume();
}
}
void pause() {
sendMessage("pause");
}
@@ -553,8 +472,11 @@ 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());
handleRoute(route);
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
route.resume();
}
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page);
@@ -623,11 +545,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.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
@@ -41,7 +40,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
BrowserTypeImpl browserType;
enum EventType {
DISCONNECTED,
@@ -61,11 +59,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 +111,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 +126,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 +170,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.recordHarPath = options.recordHarPath;
contexts.add(context);
return context;
}
@@ -230,7 +191,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);
}
@@ -246,7 +209,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
PageImpl page = context.newPage();
page.ownedContext = context;
context.ownerPage = page;
@@ -22,19 +22,14 @@ 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;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -50,9 +45,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;
return browser;
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
}
@Override
@@ -67,25 +60,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
// We don't use gson() here as the headers map should be serialized to a json object.
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("wsEndpoint", wsEndpoint);
if (!params.has("headers")) {
params.add("headers", new JsonObject());
}
JsonObject headers = params.get("headers").getAsJsonObject();
boolean foundBrowserHeader = false;
for (String name : headers.keySet()) {
if ("x-playwright-browser".equalsIgnoreCase(name)) {
foundBrowserHeader = true;
break;
}
}
if (!foundBrowserHeader) {
headers.addProperty("x-playwright-browser", name());
}
JsonObject json = sendMessage("connect", params).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env);
Connection connection = new Connection(pipe);
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
@@ -99,7 +76,6 @@ 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;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
@@ -133,7 +109,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
browser.browserType = this;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
@@ -155,52 +130,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 +173,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.recordHarPath = options.recordHarPath;
return context;
}
@@ -108,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;
}
}
@@ -16,17 +16,23 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
import static com.microsoft.playwright.impl.Serialization.gson;
class Message {
@@ -56,7 +62,7 @@ public class Connection {
private final Map<String, ChannelOwner> objects = new HashMap<>();
private final Root root;
private int lastId = 0;
private final StackTraceCollector stackTraceCollector;
private final Path srcDir;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName;
private static final boolean isLogging;
@@ -64,8 +70,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,18 +84,21 @@ public class Connection {
}
}
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);
}
boolean isCollectingStacks() {
return stackTraceCollector != null;
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
if (srcRoot == null) {
srcDir = null;
} else {
srcDir = Paths.get(srcRoot);
if (!Files.exists(srcDir)) {
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
}
}
}
String setApiName(String name) {
@@ -112,6 +119,45 @@ public class Connection {
return internalSendMessage(guid, method, params);
}
private String sourceFile(StackTraceElement frame) {
String pkg = frame.getClassName();
int lastDot = pkg.lastIndexOf('.');
if (lastDot == -1) {
pkg = "";
} else {
pkg = frame.getClassName().substring(0, lastDot + 1);
}
pkg = pkg.replace('.', File.separatorChar);
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
}
private JsonArray currentStackTrace() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int index = 0;
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
index++;
};
// Find Playwright API call
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
// hack for tests
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
break;
}
index++;
}
JsonArray jsonStack = new JsonArray();
for (; index < stack.length; index++) {
StackTraceElement frame = stack[index];
JsonObject jsonFrame = new JsonObject();
jsonFrame.addProperty("file", sourceFile(frame));
jsonFrame.addProperty("line", frame.getLineNumber());
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
jsonStack.add(jsonFrame);
}
return jsonStack;
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
int id = ++lastId;
WaitableResult<JsonElement> result = new WaitableResult<>();
@@ -122,15 +168,11 @@ public class Connection {
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
if (srcDir != null) {
metadata.add("stack", currentStackTrace());
}
if (apiName != null) {
metadata.addProperty("apiName", apiName);
// All but first message in an API call are considered internal and will be hidden from the inspector.
apiName = null;
if (stackTraceCollector != null) {
metadata.add("stack", stackTraceCollector.currentStackTrace());
}
}
message.add("metadata", metadata);
transport.send(message);
@@ -141,10 +183,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)
@@ -263,9 +301,9 @@ public class Connection {
case "ElementHandle":
result = new ElementHandleImpl(parent, type, guid, initializer);
break;
case "APIRequestContext":
case "FetchRequest":
// Create fake object as this API is experimental an only exposed in Node.js.
result = new APIRequestContextImpl(parent, type, guid, initializer);
result = new ChannelOwner(parent, type, guid, initializer);
break;
case "Frame":
result = new FrameImpl(parent, type, guid, initializer);
@@ -276,10 +314,6 @@ public class Connection {
case "JsonPipe":
result = new JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
localUtils = new LocalUtils(parent, type, guid, initializer);
result = localUtils;
break;
case "Page":
result = new PageImpl(parent, type, guid, initializer);
break;
@@ -301,18 +335,12 @@ public class Connection {
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "Tracing":
result = new TracingImpl(parent, type, guid, initializer);
break;
case "WebSocket":
result = new WebSocketImpl(parent, type, guid, initializer);
break;
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);
}
@@ -20,6 +20,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.ElementState;
@@ -33,8 +34,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.convertViaJson;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -300,7 +300,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")) {
@@ -435,9 +435,9 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setChecked(boolean checked, SetCheckedOptions options) {
if (checked) {
check(convertType(options, CheckOptions.class));
check(convertViaJson(options, CheckOptions.class));
} else {
uncheck(convertType(options, UncheckOptions.class));
uncheck(convertViaJson(options, UncheckOptions.class));
}
}
@@ -456,24 +456,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 +470,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files);
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -23,7 +23,7 @@ import com.microsoft.playwright.options.FilePayload;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class FileChooserImpl implements FileChooser {
private final PageImpl page;
@@ -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, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -1,58 +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.options.FilePayload;
import com.microsoft.playwright.options.FormData;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
public class FormDataImpl implements FormData {
Map<String, Object> fields = new LinkedHashMap<>();
@Override
public FormData set(String name, String value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, boolean value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, int value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, Path value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, FilePayload value) {
fields.put(name, value);
return this;
}
}
@@ -31,16 +31,16 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.options.WaitUntilState.*;
import static com.microsoft.playwright.options.LoadState.*;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
public class FrameImpl extends ChannelOwner implements Frame {
private String name;
private String url;
FrameImpl parentFrame;
Set<FrameImpl> childFrames = new LinkedHashSet<>();
private final Set<WaitUntilState> loadStates = new HashSet<>();
private final Set<LoadState> loadStates = new HashSet<>();
enum InternalEventType { NAVIGATED, LOADSTATE }
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
@@ -61,12 +61,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
}
private static WaitUntilState loadStateFromProtocol(String value) {
private static LoadState loadStateFromProtocol(String value) {
switch (value) {
case "load": return LOAD;
case "domcontentloaded": return DOMCONTENTLOADED;
case "networkidle": return NETWORKIDLE;
case "commit": return COMMIT;
default: throw new PlaywrightException("Unexpected value: " + value);
}
}
@@ -358,11 +357,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public FrameLocator frameLocator(String selector) {
return new FrameLocatorImpl(this, selector);
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
@@ -566,8 +560,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(this, selector, convertType(options, Locator.LocatorOptions.class));
public Locator locator(String selector) {
return new LocatorImpl(this, selector);
}
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
@@ -586,7 +580,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public PageImpl page() {
public Page page() {
return page;
}
@@ -656,9 +650,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
checkImpl(selector, convertType(options, CheckOptions.class));
checkImpl(selector, convertViaJson(options, CheckOptions.class));
} else {
uncheckImpl(selector, convertType(options, UncheckOptions.class));
uncheckImpl(selector, convertViaJson(options, UncheckOptions.class));
}
}
@@ -686,32 +680,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();
}
@@ -818,10 +801,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options);
}
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
if (options == null) {
options = new WaitForLoadStateOptions();
}
@@ -836,11 +815,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
runUntil(() -> {}, new WaitableRace<>(waitables));
}
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
private final WaitUntilState expectedState;
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
private final LoadState expectedState;
private boolean isDone;
WaitForLoadStateHelper(WaitUntilState state) {
WaitForLoadStateHelper(LoadState state) {
expectedState = state;
isDone = loadStates.contains(state);
if (!isDone) {
@@ -849,7 +828,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public void accept(WaitUntilState state) {
public void accept(LoadState state) {
if (expectedState.equals(state)) {
isDone = true;
dispose();
@@ -872,13 +851,13 @@ 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 LoadState expectedLoadState;
private WaitForLoadStateHelper loadStateHelper;
private RequestImpl request;
private RuntimeException exception;
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
this.matcher = matcher;
this.expectedLoadState = expectedLoadState;
internalListeners.add(InternalEventType.NAVIGATED, this);
@@ -956,7 +935,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
@@ -993,9 +972,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void waitForTimeoutImpl(double timeout) {
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("waitForTimeout", params);
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
@Override
public Void get() {
// Override to not throw.
return null;
}
});
}
@Override
@@ -1022,38 +1005,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
options = new WaitForURLOptions();
}
if (matcher.test(url())) {
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
convertViaJson(options, WaitForLoadStateOptions.class));
return;
}
waitForNavigationImpl(() -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
}
int queryCount(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
return result.get("value").getAsInt();
}
void highlightImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
sendMessage("highlight", params);
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
}
protected void handleEvent(String event, JsonObject params) {
if ("loadstate".equals(event)) {
JsonElement add = params.get("add");
if (add != null) {
WaitUntilState state = loadStateFromProtocol(add.getAsString());
LoadState 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");
@@ -1,57 +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.FrameLocator;
import com.microsoft.playwright.Locator;
import static com.microsoft.playwright.impl.Utils.convertType;
class FrameLocatorImpl implements FrameLocator {
private final FrameImpl frame;
private final String frameSelector;
FrameLocatorImpl(FrameImpl frame, String selector) {
this.frame = frame;
this.frameSelector = selector;
}
@Override
public FrameLocator first() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=0");
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
}
@Override
public FrameLocator last() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=-1");
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
}
@Override
public FrameLocator nth(int index) {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
}
}
@@ -1,109 +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.isApiLoggingEnabled;
import static com.microsoft.playwright.impl.LoggingSupport.logApi;
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();
if (isApiLoggingEnabled()) {
logApi("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)) {
if (isApiLoggingEnabled()) {
logApi("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);
}
}
@@ -1,352 +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.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;
import static com.microsoft.playwright.impl.Serialization.serializeArgument;
import static com.microsoft.playwright.impl.Utils.convertType;
public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions {
public LocatorAssertionsImpl(Locator locator) {
this(locator, false);
}
private LocatorAssertionsImpl(Locator locator, boolean isNot) {
super((LocatorImpl) locator, isNot);
}
@Override
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));
}
@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));
}
@Override
public void containsText(String[] strings, ContainsTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void containsText(Pattern[] patterns, ContainsTextOptions options) {
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);
}
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
hasAttribute(name, expected, text, options);
}
@Override
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
hasAttribute(name, expected, pattern, options);
}
private void hasAttribute(String name, ExpectedTextValue expectedText, Object expectedValue, HasAttributeOptions options) {
if (options == null) {
options = new HasAttributeOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
String message = "Locator expected to have attribute '" + name + "'";
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasClass(String text, HasClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(Pattern pattern, HasClassOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(String[] strings, HasClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(Pattern[] patterns, HasClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
list.add(expected);
}
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasCount(int count, HasCountOptions options) {
if (options == null) {
options = new HasCountOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = count;
List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
}
@Override
public void hasCSS(String name, String value, HasCSSOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value;
hasCSS(name, expected, value, options);
}
@Override
public void hasCSS(String name, Pattern pattern, HasCSSOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
hasCSS(name, expected, pattern, options);
}
private void hasCSS(String name, ExpectedTextValue expectedText, Object expectedValue, HasCSSOptions options) {
if (options == null) {
options = new HasCSSOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
String message = "Locator expected to have CSS property '" + name + "'";
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasId(String id, HasIdOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = id;
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasId(Pattern pattern, HasIdOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasJSProperty(String name, Object value, HasJSPropertyOptions options) {
if (options == null) {
options = new HasJSPropertyOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
commonOptions.expectedValue = serializeArgument(value);
List<ExpectedTextValue> list = null;
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
}
@Override
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));
}
@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;
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(String[] strings, HasTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(Pattern[] patterns, HasTextOptions options) {
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);
}
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(String value, HasValueOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value;
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(Pattern pattern, HasValueOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
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));
}
@Override
public void isDisabled(IsDisabledOptions options) {
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEditable(IsEditableOptions options) {
expectTrue("to.be.editable", "Locator expected to be editable", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEmpty(IsEmptyOptions options) {
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEnabled(IsEnabledOptions options) {
expectTrue("to.be.enabled", "Locator expected to be enabled", convertType(options, FrameExpectOptions.class));
}
@Override
public void isFocused(IsFocusedOptions options) {
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
}
@Override
public void isHidden(IsHiddenOptions options) {
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
}
@Override
public void isVisible(IsVisibleOptions options) {
expectTrue("to.be.visible", "Locator expected to be visible", convertType(options, FrameExpectOptions.class));
}
private void expectTrue(String expression, String message, FrameExpectOptions options) {
List<ExpectedTextValue> expectedText = null;
expectImpl(expression, expectedText, null, message, options);
}
@Override
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,90 +1,31 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Locator;
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.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
private static class Filters {
private final Map<Field, String> filterFieldToEngine = new LinkedHashMap<>();
private void addFilter(String name, String engine) throws NoSuchFieldException {
filterFieldToEngine.put(LocatorOptions.class.getField(name), engine);
}
{
try {
addFilter("has", "has");
// addFilter("leftOf", "left-of");
// addFilter("rightOf", "right-of");
// addFilter("above", "above");
// addFilter("below", "below");
// addFilter("near", "near");
} catch (NoSuchFieldException e) {
throw new InternalError(e);
}
}
String addFiltersToSelector(String selector, LocatorOptions options, Frame frame) {
try {
for (Map.Entry<Field, String> p : filterFieldToEngine.entrySet()) {
LocatorImpl filter = (LocatorImpl) p.getKey().get(options);
if (filter == null) {
continue;
}
if (filter.frame != frame) {
throw new PlaywrightException("Inner '" + p.getKey().getName() + "' locator must belong to the same frame.");
}
selector += " >> " + p.getValue() + "=" + gson().toJson(filter.selector);
}
} catch (IllegalAccessException e) {
throw new PlaywrightException("Unexpected options", e);
}
return selector;
}
}
private static final Filters filters = new Filters();
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
public LocatorImpl(FrameImpl frame, String selector) {
this.frame = frame;
if (options != null) {
if (options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
String jsRegex = "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
selector += " >> has=" + gson().toJson("text=" + jsRegex);
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
}
}
selector = filters.addFiltersToSelector(selector, options, frame);
}
this.selector = selector;
}
private static String escapeWithQuotes(String text) {
return gson().toJson(text);
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
ElementHandleOptions handleOptions = convertViaJson(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
@@ -122,7 +63,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new CheckOptions();
}
frame.check(selector, convertType(options, Frame.CheckOptions.class).setStrict(true));
frame.check(selector, convertViaJson(options, Frame.CheckOptions.class).setStrict(true));
}
@Override
@@ -130,12 +71,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ClickOptions();
}
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
frame.click(selector, convertViaJson(options, Frame.ClickOptions.class).setStrict(true));
}
@Override
public int count() {
return frame.queryCount(selector);
return ((Number) evaluateAll("ee => ee.length")).intValue();
}
@Override
@@ -143,7 +84,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new DblclickOptions();
}
frame.dblclick(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true));
frame.dblclick(selector, convertViaJson(options, Frame.DblclickOptions.class).setStrict(true));
}
@Override
@@ -151,17 +92,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new DispatchEventOptions();
}
frame.dispatchEvent(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class).setStrict(true));
}
@Override
public void dragTo(Locator target, DragToOptions options) {
if (options == null) {
options = new DragToOptions();
}
Frame.DragAndDropOptions frameOptions = convertType(options, Frame.DragAndDropOptions.class);
frameOptions.setStrict(true);
frame.dragAndDrop(selector, ((LocatorImpl) target).selector, frameOptions);
frame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class).setStrict(true));
}
@Override
@@ -169,7 +100,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ElementHandleOptions();
}
Frame.WaitForSelectorOptions frameOptions = convertType(options, Frame.WaitForSelectorOptions.class);
Frame.WaitForSelectorOptions frameOptions = convertViaJson(options, Frame.WaitForSelectorOptions.class);
frameOptions.setStrict(true);
frameOptions.setState(WaitForSelectorState.ATTACHED);
return frame.waitForSelector(selector, frameOptions);
@@ -200,17 +131,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new FillOptions();
}
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));
frame.fill(selector, value, convertViaJson(options, Frame.FillOptions.class).setStrict(true));
}
@Override
public Locator first() {
return new LocatorImpl(frame, selector + " >> nth=0", null);
return new LocatorImpl(frame, selector + " >> nth=0");
}
@Override
@@ -218,12 +144,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new FocusOptions();
}
frame.focus(selector, convertType(options, Frame.FocusOptions.class).setStrict(true));
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, this.selector + " >> " + selector);
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
}
@Override
@@ -231,12 +152,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new GetAttributeOptions();
}
return frame.getAttribute(selector, name, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
}
@Override
public void highlight() {
frame.highlightImpl(selector);
return frame.getAttribute(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class).setStrict(true));
}
@Override
@@ -244,7 +160,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new HoverOptions();
}
frame.hover(selector, convertType(options, Frame.HoverOptions.class).setStrict(true));
frame.hover(selector, convertViaJson(options, Frame.HoverOptions.class).setStrict(true));
}
@Override
@@ -252,7 +168,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InnerHTMLOptions();
}
return frame.innerHTML(selector, convertType(options, Frame.InnerHTMLOptions.class).setStrict(true));
return frame.innerHTML(selector, convertViaJson(options, Frame.InnerHTMLOptions.class).setStrict(true));
}
@Override
@@ -260,7 +176,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InnerTextOptions();
}
return frame.innerText(selector, convertType(options, Frame.InnerTextOptions.class).setStrict(true));
return frame.innerText(selector, convertViaJson(options, Frame.InnerTextOptions.class).setStrict(true));
}
@Override
@@ -268,7 +184,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InputValueOptions();
}
return frame.inputValue(selector, convertType(options, Frame.InputValueOptions.class).setStrict(true));
return frame.inputValue(selector, convertViaJson(options, Frame.InputValueOptions.class).setStrict(true));
}
@Override
@@ -276,7 +192,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsCheckedOptions();
}
return frame.isChecked(selector, convertType(options, Frame.IsCheckedOptions.class).setStrict(true));
return frame.isChecked(selector, convertViaJson(options, Frame.IsCheckedOptions.class).setStrict(true));
}
@Override
@@ -284,7 +200,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsDisabledOptions();
}
return frame.isDisabled(selector, convertType(options, Frame.IsDisabledOptions.class).setStrict(true));
return frame.isDisabled(selector, convertViaJson(options, Frame.IsDisabledOptions.class).setStrict(true));
}
@Override
@@ -292,7 +208,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsEditableOptions();
}
return frame.isEditable(selector, convertType(options, Frame.IsEditableOptions.class).setStrict(true));
return frame.isEditable(selector, convertViaJson(options, Frame.IsEditableOptions.class).setStrict(true));
}
@Override
@@ -300,7 +216,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsEnabledOptions();
}
return frame.isEnabled(selector, convertType(options, Frame.IsEnabledOptions.class).setStrict(true));
return frame.isEnabled(selector, convertViaJson(options, Frame.IsEnabledOptions.class).setStrict(true));
}
@Override
@@ -308,7 +224,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsHiddenOptions();
}
return frame.isHidden(selector, convertType(options, Frame.IsHiddenOptions.class).setStrict(true));
return frame.isHidden(selector, convertViaJson(options, Frame.IsHiddenOptions.class).setStrict(true));
}
@Override
@@ -316,27 +232,22 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsVisibleOptions();
}
return frame.isVisible(selector, convertType(options, Frame.IsVisibleOptions.class).setStrict(true));
return frame.isVisible(selector, convertViaJson(options, Frame.IsVisibleOptions.class).setStrict(true));
}
@Override
public Locator last() {
return new LocatorImpl(frame, selector + " >> nth=-1", null);
return new LocatorImpl(frame, selector + " >> nth=-1");
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(frame, this.selector + " >> " + selector, options);
public Locator locator(String selector) {
return new LocatorImpl(frame, this.selector + " >> " + selector);
}
@Override
public Locator nth(int index) {
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
}
@Override
public Page page() {
return frame.page();
return new LocatorImpl(frame, selector + " >> nth=" + index);
}
@Override
@@ -344,12 +255,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new PressOptions();
}
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
frame.press(selector, key, convertViaJson(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
return withElement((h, o) -> h.screenshot(o), convertViaJson(options, ElementHandle.ScreenshotOptions.class));
}
@Override
@@ -357,7 +268,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o);
return null;
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}, convertViaJson(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}
@Override
@@ -365,7 +276,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -373,7 +284,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -381,7 +292,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -389,7 +300,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -397,7 +308,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -405,7 +316,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -413,7 +324,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.selectText(o);
return null;
}, convertType(options, ElementHandle.SelectTextOptions.class));
}, convertViaJson(options, ElementHandle.SelectTextOptions.class));
}
@Override
@@ -421,7 +332,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetCheckedOptions();
}
frame.setChecked(selector, checked, convertType(options, Frame.SetCheckedOptions.class).setStrict(true));
frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class).setStrict(true));
}
@Override
@@ -429,7 +340,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -437,7 +348,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -445,7 +356,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -453,7 +364,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -461,7 +372,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TapOptions();
}
frame.tap(selector, convertType(options, Frame.TapOptions.class).setStrict(true));
frame.tap(selector, convertViaJson(options, Frame.TapOptions.class).setStrict(true));
}
@Override
@@ -469,7 +380,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TextContentOptions();
}
return frame.textContent(selector, convertType(options, Frame.TextContentOptions.class).setStrict(true));
return frame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class).setStrict(true));
}
@Override
@@ -477,7 +388,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TypeOptions();
}
frame.type(selector, text, convertType(options, Frame.TypeOptions.class).setStrict(true));
frame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class).setStrict(true));
}
@Override
@@ -485,7 +396,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new UncheckOptions();
}
frame.uncheck(selector, convertType(options, Frame.UncheckOptions.class).setStrict(true));
frame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class).setStrict(true));
}
@Override
@@ -497,34 +408,11 @@ class LocatorImpl implements Locator {
}
private void waitForImpl(WaitForOptions options) {
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
}
@Override
public String toString() {
return "Locator@" + selector;
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
}
JsonObject toProtocol() {
JsonObject result = new JsonObject();
result.add("frame", frame.toProtocolRef());
result.addProperty("selector", selector);
return result;
}
private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) {
if (options == null) {
options = new FrameExpectOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", expression);
JsonElement json = frame.sendMessage("expect", params);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
}
@@ -60,11 +60,7 @@ class LoggingSupport {
System.err.println(timestamp + " " + message);
}
static boolean isApiLoggingEnabled() {
return isEnabled;
}
static void logApi(String message) {
private void logApi(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:api " + message);
}
@@ -20,7 +20,7 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Mouse;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class MouseImpl implements Mouse {
private final ChannelOwner page;
@@ -54,7 +54,7 @@ class MouseImpl implements Mouse {
if (options == null) {
clickOptions = new ClickOptions();
} else {
clickOptions = convertType(options, ClickOptions.class);
clickOptions = convertViaJson(options, ClickOptions.class);
}
clickOptions.clickCount = 2;
click(x, y, clickOptions);
@@ -1,74 +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.Page;
import com.microsoft.playwright.assertions.PageAssertions;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
import static com.microsoft.playwright.impl.Utils.convertType;
public class PageAssertionsImpl extends AssertionsBase implements PageAssertions {
private final PageImpl actualPage;
public PageAssertionsImpl(Page page) {
this(page, false);
}
private PageAssertionsImpl(Page page, boolean isNot) {
super((LocatorImpl) page.locator(":root"), isNot);
this.actualPage = (PageImpl) page;
}
@Override
public void hasTitle(String title, HasTitleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = title;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasTitle(Pattern pattern, HasTitleOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasURL(String url, HasURLOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
if (actualPage.context().baseUrl != null) {
url = resolveUrl(actualPage.context().baseUrl, url);
}
expected.string = url;
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasURL(Pattern pattern, HasURLOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class));
}
@Override
public PageAssertions not() {
return new PageAssertionsImpl(actualPage, !isNot);
}
}
@@ -28,11 +28,11 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
@@ -170,11 +170,13 @@ public class PageImpl extends ChannelOwner implements Page {
try {
bindingCall.call(binding);
} catch (RuntimeException e) {
if (!isSafeCloseError(e.getMessage())) {
logWithTimestamp(e.getMessage());
}
e.printStackTrace();
}
}
} 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,13 +196,13 @@ 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) {
maybeDisableNetworkInterception();
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
handled = browserContext.routes.handle(route);
}
if (!route.isHandled()) {
browserContext.handleRoute(route);
if (!handled) {
route.resume();
}
} else if ("video".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
@@ -555,7 +557,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
selector, convertType(options, Frame.QuerySelectorOptions.class)));
selector, convertViaJson(options, Frame.QuerySelectorOptions.class)));
}
@Override
@@ -566,7 +568,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class)));
selector, pageFunction, arg, convertViaJson(options, Frame.EvalOnSelectorOptions.class)));
}
@Override
@@ -600,13 +602,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options) {
return withLogging("Page.addScriptTag",
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
() -> mainFrame.addScriptTagImpl(convertViaJson(options, Frame.AddScriptTagOptions.class)));
}
@Override
public ElementHandle addStyleTag(AddStyleTagOptions options) {
return withLogging("Page.addStyleTag",
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
() -> mainFrame.addStyleTagImpl(convertViaJson(options, Frame.AddStyleTagOptions.class)));
}
@Override
@@ -617,13 +619,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void check(String selector, CheckOptions options) {
withLogging("Page.check",
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
() -> mainFrame.checkImpl(selector, convertViaJson(options, Frame.CheckOptions.class)));
}
@Override
public void click(String selector, ClickOptions options) {
withLogging("Page.click",
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
() -> mainFrame.clickImpl(selector, convertViaJson(options, Frame.ClickOptions.class)));
}
@Override
@@ -639,13 +641,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void dblclick(String selector, DblclickOptions options) {
withLogging("Page.dblclick",
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
() -> mainFrame.dblclickImpl(selector, convertViaJson(options, Frame.DblclickOptions.class)));
}
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Page.dispatchEvent",
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class)));
}
@Override
@@ -702,13 +704,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void fill(String selector, String value, FillOptions options) {
withLogging("Page.fill",
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
() -> mainFrame.fillImpl(selector, value, convertViaJson(options, Frame.FillOptions.class)));
}
@Override
public void focus(String selector, FocusOptions options) {
withLogging("Page.focus",
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
() -> mainFrame.focusImpl(selector, convertViaJson(options, Frame.FocusOptions.class)));
}
@Override
@@ -736,11 +738,6 @@ public class PageImpl extends ChannelOwner implements Page {
return frameFor(new UrlMatcher(predicate));
}
@Override
public FrameLocator frameLocator(String selector) {
return mainFrame.frameLocator(selector);
}
private Frame frameFor(UrlMatcher matcher) {
for (Frame frame : frames) {
if (matcher.test(frame.url())) {
@@ -758,7 +755,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public String getAttribute(String selector, String name, GetAttributeOptions options) {
return withLogging("Page.getAttribute",
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
() -> mainFrame.getAttributeImpl(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class)));
}
@Override
@@ -797,41 +794,44 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
return withLogging("Page.navigate", () ->
mainFrame.navigateImpl(url, convertViaJson(options, Frame.NavigateOptions.class)));
}
@Override
public void hover(String selector, HoverOptions options) {
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class)));
withLogging("Page.hover", () ->
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class)));
withLogging("Page.dragAndDrop", () ->
mainFrame.dragAndDropImpl(source, target, convertViaJson(options, Frame.DragAndDropOptions.class)));
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Page.innerHTML",
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
() -> mainFrame.innerHTMLImpl(selector, convertViaJson(options, Frame.InnerHTMLOptions.class)));
}
@Override
public String innerText(String selector, InnerTextOptions options) {
return withLogging("Page.innerText",
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Page.inputValue",
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
() -> mainFrame.inputValueImpl(selector, convertViaJson(options, Frame.InputValueOptions.class)));
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked",
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
() -> mainFrame.isCheckedImpl(selector, convertViaJson(options, Frame.IsCheckedOptions.class)));
}
@Override
@@ -842,31 +842,31 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled",
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
() -> mainFrame.isDisabledImpl(selector, convertViaJson(options, Frame.IsDisabledOptions.class)));
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable",
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
() -> mainFrame.isEditableImpl(selector, convertViaJson(options, Frame.IsEditableOptions.class)));
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled",
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
() -> mainFrame.isEnabledImpl(selector, convertViaJson(options, Frame.IsEnabledOptions.class)));
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden",
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
() -> mainFrame.isHiddenImpl(selector, convertViaJson(options, Frame.IsHiddenOptions.class)));
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible",
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
() -> mainFrame.isVisibleImpl(selector, convertViaJson(options, Frame.IsVisibleOptions.class)));
}
@Override
@@ -875,8 +875,8 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class));
public Locator locator(String selector) {
return mainFrame.locator(selector);
}
@Override
@@ -929,7 +929,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void press(String selector, String key, PressOptions options) {
withLogging("Page.press",
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
() -> mainFrame.pressImpl(selector, key, convertViaJson(options, Frame.PressOptions.class)));
}
@Override
@@ -937,11 +937,6 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.reload", () -> reloadImpl(options));
}
@Override
public APIRequestContextImpl request() {
return browserContext.request();
}
private Response reloadImpl(ReloadOptions options) {
if (options == null) {
options = new ReloadOptions();
@@ -969,21 +964,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);
@@ -1046,18 +1026,8 @@ public class PageImpl extends ChannelOwner implements Page {
}
}
}
List<Locator> mask = options.mask;
options.mask = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
options.mask = mask;
params.remove("path");
if (mask != null) {
JsonArray maskArray = new JsonArray();
for (Locator locator: mask) {
maskArray.add(((LocatorImpl) locator).toProtocol());
}
params.add("mask", maskArray);
}
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
@@ -1070,25 +1040,25 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Page.setChecked",
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
() -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class)));
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Page.setContent",
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
() -> mainFrame.setContentImpl(html, convertViaJson(options, Frame.SetContentOptions.class)));
}
@Override
@@ -1135,7 +1105,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles",
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
}
@Override
@@ -1146,7 +1116,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles",
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
}
@Override
@@ -1162,13 +1132,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void tap(String selector, TapOptions options) {
withLogging("Page.tap",
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
() -> mainFrame.tapImpl(selector, convertViaJson(options, Frame.TapOptions.class)));
}
@Override
public String textContent(String selector, TextContentOptions options) {
return withLogging("Page.textContent",
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
() -> mainFrame.textContentImpl(selector, convertViaJson(options, Frame.TextContentOptions.class)));
}
@Override
@@ -1184,13 +1154,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void type(String selector, String text, TypeOptions options) {
withLogging("Page.type",
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
() -> mainFrame.typeImpl(selector, text, convertViaJson(options, Frame.TypeOptions.class)));
}
@Override
public void uncheck(String selector, UncheckOptions options) {
withLogging("Page.uncheck",
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
() -> mainFrame.uncheckImpl(selector, convertViaJson(options, Frame.UncheckOptions.class)));
}
@Override
@@ -1211,18 +1181,14 @@ public class PageImpl extends ChannelOwner implements Page {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("Page.unroute", () -> {
routes.remove(matcher, handler);
maybeDisableNetworkInterception();
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
@Override
public String url() {
return mainFrame.url();
@@ -1263,13 +1229,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Page.waitForFunction",
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertViaJson(options, Frame.WaitForFunctionOptions.class)));
}
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Page.waitForLoadState", () -> {
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class));
mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class));
return null;
});
}
@@ -1403,7 +1369,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Page.waitForSelector",
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
() -> mainFrame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class)));
}
@Override
@@ -1427,7 +1393,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertType(options, Frame.WaitForURLOptions.class)));
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
}
@Override
@@ -17,7 +17,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
@@ -37,13 +36,12 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
if (options != null && options.env != null) {
env = options.env;
}
Path driver = Driver.ensureDriverInstalled(env, true);
Path driver = Driver.ensureDriverInstalled(env);
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);
@@ -57,9 +55,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
private final LocalUtils localUtils;
private SharedSelectors sharedSelectors;
private SharedSelectors sharedSelectors;;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -68,17 +64,12 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
apiRequest = new APIRequestImpl(this);
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
chromium.localUtils = localUtils;
firefox.localUtils = localUtils;
webkit.localUtils = localUtils;
}
void initSharedSelectors(PlaywrightImpl parent) {
assert sharedSelectors == null;
if (parent == null) {
sharedSelectors = new SharedSelectors();
sharedSelectors = new SharedSelectors();;
} else {
sharedSelectors = parent.sharedSelectors;
}
@@ -99,11 +90,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
return firefox;
}
@Override
public APIRequest request() {
return apiRequest;
}
@Override
public BrowserTypeImpl webkit() {
return webkit;
@@ -18,12 +18,18 @@
package com.microsoft.playwright.impl;
import java.util.List;
class Binary {
}
class Channel {
String guid;
}
class Metadata{
String stack;
}
class SerializedValue{
Number n;
Boolean b;
@@ -31,7 +37,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,15 +49,47 @@ class SerializedValue{
}
O[] o;
Number h;
Integer id;
Integer ref;
}
class SerializedArgument{
SerializedValue value;
Channel[] handles;
}
class AXNode{
String role;
String name;
String valueString;
Number valueNumber;
String description;
String keyshortcuts;
String roledescription;
String valuetext;
Boolean disabled;
Boolean expanded;
Boolean focused;
Boolean modal;
Boolean multiline;
Boolean multiselectable;
Boolean readonly;
Boolean required;
Boolean selected;
// Possible values: { 'checked, 'unchecked, 'mixed }
String checked;
// Possible values: { 'pressed, 'released, 'mixed }
String pressed;
Number level;
Number valuemin;
Number valuemax;
String autocomplete;
String haspopup;
String invalid;
String orientation;
AXNode[] children;
}
class SerializedError{
public static class Error {
String message;
@@ -82,29 +119,3 @@ class SerializedError{
}
}
class ExpectedTextValue {
String string;
String regexSource;
String regexFlags;
Boolean ignoreCase;
Boolean matchSubstring;
Boolean normalizeWhiteSpace;
}
class FrameExpectOptions {
Object expressionArg;
List<ExpectedTextValue> expectedText;
Integer expectedNumber;
SerializedArgument expectedValue;
Boolean useInnerText;
boolean isNot;
Double timeout;
}
class FrameExpectResult {
boolean matches;
SerializedValue received;
List<String> log;
}
@@ -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;
}
}
@@ -1,121 +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.options.FormData;
import com.microsoft.playwright.options.RequestOptions;
import java.util.LinkedHashMap;
import java.util.Map;
public class RequestOptionsImpl implements RequestOptions {
Map<String, Object> params;
String method;
Map<String, String> headers;
Object data;
FormDataImpl form;
FormDataImpl multipart;
Boolean failOnStatusCode;
Boolean ignoreHTTPSErrors;
Double timeout;
@Override
public RequestOptions setHeader(String name, String value) {
if (headers == null) {
headers = new LinkedHashMap<>();
}
headers.put(name, value);
return this;
}
@Override
public RequestOptions setData(String data) {
this.data = data;
return this;
}
@Override
public RequestOptions setData(byte[] data) {
this.data = data;
return this;
}
@Override
public RequestOptions setData(Object data) {
this.data = data;
return this;
}
@Override
public RequestOptions setForm(FormData form) {
this.form = (FormDataImpl) form;
return this;
}
@Override
public RequestOptions setMethod(String method) {
this.method = method;
return this;
}
@Override
public RequestOptions setMultipart(FormData form) {
this.multipart = (FormDataImpl) form;
return this;
}
@Override
public RequestOptions setQueryParam(String name, String value) {
return setQueryParamImpl(name, value);
}
@Override
public RequestOptions setQueryParam(String name, boolean value) {
return setQueryParamImpl(name, value);
}
@Override
public RequestOptions setQueryParam(String name, int value) {
return setQueryParamImpl(name, value);
}
private RequestOptions setQueryParamImpl(String name, Object value) {
if (params == null) {
params = new LinkedHashMap<>();
}
params.put(name, value);
return this;
}
@Override
public RequestOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
@Override
public RequestOptions setFailOnStatusCode(boolean failOnStatusCode) {
this.failOnStatusCode = failOnStatusCode;
return this;
}
@Override
public RequestOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
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,8 +17,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Route;
import java.io.IOException;
@@ -28,91 +28,56 @@ 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 boolean handled;
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void abort(String errorCode) {
startHandling();
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessageAsync("abort", params);
sendMessage("abort", params);
});
}
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);
}
sendMessageAsync("continue", params);
}
private static byte[] getPostDataBytes(Object postData) {
if (postData instanceof byte[]) {
return (byte[]) postData;
if (options.method != null) {
params.addProperty("method", options.method);
}
if (postData instanceof String) {
return ((String) postData).getBytes(StandardCharsets.UTF_8);
if (options.headers != null) {
params.add("headers", Serialization.toProtocol(options.headers));
}
throw new PlaywrightException("postData must be either String or byte[], found: " + postData.getClass().getName());
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);
}
sendMessage("continue", params);
}
@Override
public void fulfill(FulfillOptions options) {
startHandling();
withLogging("Route.fulfill", () -> fulfillImpl(options));
}
@@ -121,30 +86,16 @@ public class RouteImpl extends ChannelOwner implements Route {
options = new FulfillOptions();
}
Integer status = options.status;
Map<String, String> headersOption = options.headers;
String fetchResponseUid = null;
if (options.response != null) {
if (status == null) {
status = options.response.status();
}
if (headersOption == null) {
headersOption = options.response.headers();
}
}
if (status == null) {
status = 200;
}
String body = null;
int status = options.status == null ? 200 : options.status;
String body = "";
boolean isBase64 = false;
int length = 0;
if (options.path != null) {
try {
byte[] buffer = Files.readAllBytes(options.path);
body = Base64.getEncoder().encodeToString(buffer);
isBase64 = true;
length = buffer.length;
byte[] buffer = Files.readAllBytes(options.path);
body = Base64.getEncoder().encodeToString(buffer);
isBase64 = true;
length = buffer.length;
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file: " + options.path, e);
}
@@ -156,22 +107,11 @@ public class RouteImpl extends ChannelOwner implements Route {
body = Base64.getEncoder().encodeToString(options.bodyBytes);
isBase64 = true;
length = options.bodyBytes.length;
} else if (options.response != null) {
APIResponseImpl response = (APIResponseImpl) options.response;
if (response.context.connection == connection) {
fetchResponseUid = response.fetchUid();
} else {
byte[] bodyBytes = response.body();
body = Base64.getEncoder().encodeToString(bodyBytes);
isBase64 = true;
length = bodyBytes.length;
}
}
Map<String, String> headers = new LinkedHashMap<>();
if (headersOption != null) {
for (Map.Entry<String, String> h : headersOption.entrySet()) {
if (options.headers != null) {
for (Map.Entry<String, String> h : options.headers.entrySet()) {
headers.put(h.getKey().toLowerCase(), h.getValue());
}
}
@@ -188,29 +128,11 @@ public class RouteImpl extends ChannelOwner implements Route {
params.add("headers", Serialization.toProtocol(headers));
params.addProperty("isBase64", isBase64);
params.addProperty("body", body);
if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid);
}
sendMessageAsync("fulfill", params);
sendMessage("fulfill", params);
}
@Override
public RequestImpl request() {
public Request request() {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
void redirectNavigationRequest(String redirectURL) {
startHandling();
JsonObject params = new JsonObject();
params.addProperty("url", redirectURL);
// TODO: _raceWithPageClose ?
sendMessageAsync("redirectNavigationRequest", params);
}
private void startHandling() {
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
handled = true;
}
}
@@ -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,7 +37,10 @@ class Router {
this.times = times;
}
boolean handle(RouteImpl route) {
boolean handle(Route route) {
if (times != null && times <= 0) {
return false;
}
if (!matcher.test(route.request().url())) {
return false;
}
@@ -68,21 +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();
boolean handle(Route route) {
for (RouteInfo info : routes) {
if (info.handle(route)) {
result = HandleResult.FoundMatchingHandler;
if (info.isDone()) {
it.remove();
}
if (route.isHandled()) {
break;
routes.remove(info);
}
return true;
}
}
return result;
return false;
}
}
@@ -28,30 +28,19 @@ 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.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 Gson gson = new GsonBuilder()
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
.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>())
@@ -61,7 +50,7 @@ class Serialization {
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();;
static Gson gson() {
return gson;
@@ -79,136 +68,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 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());
@@ -219,17 +154,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":
@@ -250,17 +174,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;
}
@@ -287,72 +209,39 @@ 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) {
jsonFiles.add(toProtocol(p));
JsonObject jsonFile = new JsonObject();
jsonFile.addProperty("name", p.name);
jsonFile.addProperty("mimeType", p.mimeType);
jsonFile.addProperty("buffer", Base64.getEncoder().encodeToString(p.buffer));
jsonFiles.add(jsonFile);
}
return jsonFiles;
}
static JsonObject toProtocol(FilePayload p) {
JsonObject jsonFile = new JsonObject();
jsonFile.addProperty("name", p.name);
jsonFile.addProperty("mimeType", p.mimeType);
jsonFile.addProperty("buffer", Base64.getEncoder().encodeToString(p.buffer));
return jsonFile;
}
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;
}
static JsonArray toProtocol(Map<String, String> map) {
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()) {
for (Map.Entry<String, String> e : map.entrySet()) {
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
item.add("value", gson().toJsonTree(e.getValue()));
item.addProperty("value", e.getValue());
array.add(item);
}
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) {
@@ -383,7 +272,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;
}
}
@@ -1,122 +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.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
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);
}
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() + "'");
}
}
return new StackTraceCollector(srcDirs);
}
private StackTraceCollector(List<Path> srcDirs) {
this.srcDirs = srcDirs;
}
private String sourceFile(StackTraceElement frame) {
String pkg = frame.getClassName();
int lastDot = pkg.lastIndexOf('.');
if (lastDot == -1) {
pkg = "";
} else {
pkg = frame.getClassName().substring(0, lastDot + 1);
}
pkg = pkg.replace('.', File.separatorChar);
String file = frame.getFileName();
if (file == null) {
return "";
}
return resolveSourcePath(Paths.get(pkg).resolve(file));
}
private String resolveSourcePath(Path relativePath) {
String path = classToSourceCache.get(relativePath);
if (path == null) {
for (Path dir : srcDirs) {
Path absolutePath = dir.resolve(relativePath);
if (Files.exists(absolutePath)) {
path = absolutePath.toString();
classToSourceCache.put(relativePath, path);
break;
}
}
if (path == null) {
path = "";
classToSourceCache.put(relativePath, path);
}
}
return path;
}
JsonArray currentStackTrace() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int index = 0;
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
index++;
};
// Find Playwright API call
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
// hack for tests
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
break;
}
index++;
}
JsonArray jsonStack = new JsonArray();
for (; index < stack.length; index++) {
StackTraceElement frame = stack[index];
JsonObject jsonFrame = new JsonObject();
jsonFrame.addProperty("file", sourceFile(frame));
jsonFrame.addProperty("line", frame.getLineNumber());
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
jsonStack.add(jsonFrame);
}
return jsonStack;
}
}
@@ -16,100 +16,70 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Tracing;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl extends ChannelOwner implements Tracing {
boolean isRemote;
class TracingImpl implements Tracing {
private final BrowserContextImpl context;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
TracingImpl(BrowserContextImpl context) {
this.context = context;
}
private void stopChunkImpl(Path path) {
JsonObject params = new JsonObject();
String mode = "doNotSave";
if (path != null) {
if (isRemote) {
mode = "compressTrace";
} else {
mode = "compressTraceAndSources";
}
}
params.addProperty("mode", mode);
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
params.addProperty("save", path != null);
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
if (!json.has("artifact")) {
return;
}
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (isRemote) {
if (context.browser() != null && context.browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(path);
artifact.delete();
// Add local sources to the remote trace if necessary.
if (isRemote && json.has("sourceEntries")) {
JsonArray entries = json.getAsJsonArray("sourceEntries");
connection.localUtils.zip(path, entries);
}
}
@Override
public void start(StartOptions options) {
withLogging("Tracing.start", () -> startImpl(options));
context.withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk(StartChunkOptions options) {
withLogging("Tracing.startChunk", () -> {
startChunkImpl(options);
public void startChunk() {
context.withLogging("Tracing.startChunk", () -> {
context.sendMessage("tracingStartChunk");
});
}
private void startChunkImpl(StartChunkOptions options) {
if (options == null) {
options = new StartChunkOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("tracingStartChunk", params);
}
private void startImpl(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
boolean includeSources = options.sources != null && options.sources;
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");
}
params.addProperty("sources", true);
}
sendMessage("tracingStart", params);
sendMessage("tracingStartChunk");
context.sendMessage("tracingStart", params);
context.sendMessage("tracingStartChunk");
}
@Override
public void stop(StopOptions options) {
withLogging("Tracing.stop", () -> {
context.withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
sendMessage("tracingStop");
context.sendMessage("tracingStop");
});
}
@Override
public void stopChunk(StopChunkOptions options) {
withLogging("Tracing.stopChunk", () -> {
context.withLogging("Tracing.stopChunk", () -> {
stopChunkImpl(options == null ? null : options.path);
});
}
@@ -54,7 +54,7 @@ class UrlMatcher {
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
}
static String resolveUrl(URL baseUrl, String spec) {
private static String resolveUrl(URL baseUrl, String spec) {
if (baseUrl == null) {
return spec;
}
@@ -16,8 +16,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.*;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
@@ -25,57 +24,32 @@ 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.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
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) {
return null;
}
// TODO: generate converter.
static <F, T> T convertViaJson(F f, Class<T> t) {
Gson gson = new GsonBuilder()
// Necessary to avoid access to private fields/classes,
// see https://github.com/microsoft/playwright-java/issues/423
.registerTypeAdapter(Optional.class, new OptionalSerializer())
.create();
String json = gson.toJson(f);
return gson.fromJson(json, t);
}
// Make sure shallow copy is sufficient
if (!t.getSuperclass().equals(Object.class) && !t.getSuperclass().equals(Enum.class)) {
throw new PlaywrightException("Cannot convert to " + t.getCanonicalName() + " that has superclass " + t.getSuperclass().getCanonicalName());
}
if (!f.getClass().getSuperclass().equals(t.getSuperclass())) {
throw new PlaywrightException("Cannot convert from " + t.getCanonicalName() + " that has superclass " + t.getSuperclass().getCanonicalName());
}
if (f instanceof Enum) {
return (T) Enum.valueOf((Class) t, ((Enum) f).name());
}
try {
T result = t.getDeclaredConstructor().newInstance();
for (Field toField : t.getDeclaredFields()) {
// Skip fields added by test coverage tools, see https://github.com/microsoft/playwright-java/issues/802
if (toField.isSynthetic()) {
continue;
}
if (Modifier.isStatic(toField.getModifiers())) {
throw new RuntimeException("Unexpected field modifiers: " + t.getCanonicalName() + "." + toField.getName() + ", modifiers: " + toField.getModifiers());
}
try {
Field fromField = f.getClass().getDeclaredField(toField.getName());
Object value = fromField.get(f);
if (value != null) {
toField.set(result, value);
}
} catch (NoSuchFieldException e) {
continue;
}
private static class OptionalSerializer implements JsonSerializer<Optional> {
@Override
public JsonElement serialize(Optional src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
if (src.isPresent()) {
result.add("value", context.serialize(src.get()));
}
return result;
} catch (Exception e) {
throw new PlaywrightException("Internal error", e);
}
}
@@ -88,7 +62,7 @@ class Utils {
for (int i = 0; i < glob.length(); ++i) {
char c = glob.charAt(i);
if (escapeGlobChars.contains(c)) {
tokens.append("\\").append(c);
tokens.append("\\" + c);
continue;
}
if (c == '*') {
@@ -126,7 +100,7 @@ class Utils {
tokens.append('|');
break;
}
tokens.append("\\").append(c);
tokens.append("\\" + c);
break;
default:
tokens.append(c);
@@ -149,74 +123,20 @@ 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) {
payloads.add(toFilePayload(file));
byte[] buffer;
try {
buffer = Files.readAllBytes(file);
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file", e);
}
payloads.add(new FilePayload(file.getFileName().toString(), mimeType(file), buffer));
}
return payloads.toArray(new FilePayload[0]);
}
static FilePayload toFilePayload(Path file) {
byte[] buffer;
try {
buffer = Files.readAllBytes(file);
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file", e);
}
return new FilePayload(file.getFileName().toString(), null, buffer);
}
static void mkParentDirs(Path file) {
Path dir = file.getParent();
if (dir != null) {
@@ -224,7 +144,7 @@ class Utils {
try {
Files.createDirectories(dir);
} catch (IOException e) {
throw new PlaywrightException("Failed to create parent directory: " + dir, e);
throw new PlaywrightException("Failed to create parent directory: " + dir.toString(), e);
}
}
}
@@ -257,7 +177,7 @@ class Utils {
}
static boolean isSafeCloseError(String error) {
return error.contains("Browser has been closed") || error.contains("Target page, context or browser has been closed");
return error.endsWith("Browser has been closed") || error.endsWith("Target page, context or browser has been closed");
}
static String createGuid() {
@@ -276,49 +196,4 @@ 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) {
// Case-insensitive search.
regexFlags += "i";
}
if ((pattern.flags() & Pattern.DOTALL) != 0) {
// Allows . to match newline characters.
regexFlags += "s";
}
if ((pattern.flags() & Pattern.MULTILINE) != 0) {
// Multi-line search.
regexFlags += "m";
}
if ((pattern.flags() & ~(Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL)) != 0) {
throw new PlaywrightException("Unexpected RegEx flag, only CASE_INSENSITIVE, DOTALL and MULTILINE are supported.");
}
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;
}
}
@@ -22,4 +22,7 @@ interface Waitable<T> {
boolean isDone();
T get();
void dispose();
default <U> Waitable<U> apply(Function<T, U> transform) {
return new WaitableAdapter<T, U>(this, transform);
}
}
@@ -16,20 +16,28 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.util.function.Function;
import java.nio.file.Path;
class WaitableAdapter<F, T> implements Waitable<T> {
private final Waitable<F> waitable;
private final Function<F, T> transformation;
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
WaitableAdapter(Waitable<F> waitable, Function<F, T> transformation) {
this.waitable = waitable;
this.transformation = transformation;
}
@Override
public boolean isDone() {
return waitable.isDone();
}
void zip(Path zipFile, JsonArray entries) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
params.add("entries", entries);
sendMessage("zip", params);
@Override
public T get() {
return transformation.apply(waitable.get());
}
@Override
public void dispose() {
waitable.dispose();
}
}
@@ -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,77 +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;
import com.microsoft.playwright.impl.FormDataImpl;
import java.nio.file.Path;
/**
* The {@code FormData} is used create form data that is sent via {@code APIRequestContext}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* .set("firstName", "John")
* .set("lastName", "Doe")
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*/
public interface FormData {
/**
* Creates new instance of {@code FormData}.
*/
static FormData create() {
return new FormDataImpl();
}
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
*
* @param name Field name.
* @param value Field value.
*/
FormData set(String name, String value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
*
* @param name Field name.
* @param value Field value.
*/
FormData set(String name, boolean value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
*
* @param name Field name.
* @param value Field value.
*/
FormData set(String name, int value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
*
* @param name Field name.
* @param value Field value.
*/
FormData set(String name, Path value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
*
* @param name Field name.
* @param value Field value.
*/
FormData set(String name, FilePayload value);
}
@@ -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
}
@@ -1,161 +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;
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.
* <pre>{@code
* context.request().post(
* "https://example.com/submit",
* RequestOptions.create()
* .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 {
/**
* Creates new instance of {@code RequestOptions}.
*/
static RequestOptions create() {
return new RequestOptionsImpl();
}
/**
* Sets the request's post data.
*
* @param data Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and
* {@code content-type} header will be set to {@code application/json} if not explicitly set. Otherwise the {@code content-type} header will
* be set to {@code application/octet-stream} if not explicitly set.
*/
RequestOptions setData(String data);
/**
* Sets the request's post data.
*
* @param data Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and
* {@code content-type} header will be set to {@code application/json} if not explicitly set. Otherwise the {@code content-type} header will
* be set to {@code application/octet-stream} if not explicitly set.
*/
RequestOptions setData(byte[] data);
/**
* Sets the request's post data.
*
* @param data Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and
* {@code content-type} header will be set to {@code application/json} if not explicitly set. Otherwise the {@code content-type} header will
* be set to {@code application/octet-stream} if not explicitly set.
*/
RequestOptions setData(Object data);
/**
*
*
* @param failOnStatusCode Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
RequestOptions setFailOnStatusCode(boolean failOnStatusCode);
/**
* Provides {@code FormData} object that will be serialized as html form using {@code application/x-www-form-urlencoded} encoding and
* sent as this request body. If this parameter is specified {@code content-type} header will be set to
* {@code application/x-www-form-urlencoded} unless explicitly provided.
*
* @param form Form data to be serialized as html form using {@code application/x-www-form-urlencoded} encoding and sent as this request
* body.
*/
RequestOptions setForm(FormData form);
/**
* Sets an HTTP header to the request.
*
* @param name Header name.
* @param value Header value.
*/
RequestOptions setHeader(String name, String value);
/**
*
*
* @param ignoreHTTPSErrors Whether to ignore HTTPS errors when sending network requests.
*/
RequestOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors);
/**
* 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>).
*
* @param method Request method, e.g. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a>.
*/
RequestOptions setMethod(String method);
/**
* Provides {@code FormData} object that will be serialized as html form using {@code multipart/form-data} encoding and sent as this
* request body. If this parameter is specified {@code content-type} header will be set to {@code multipart/form-data} unless
* explicitly provided.
*
* @param form Form data to be serialized as html form using {@code multipart/form-data} encoding and sent as this request body.
*/
RequestOptions setMultipart(FormData form);
/**
* Adds a query parameter to the request URL.
*
* @param name Parameter name.
* @param value Parameter value.
*/
RequestOptions setQueryParam(String name, String value);
/**
* Adds a query parameter to the request URL.
*
* @param name Parameter name.
* @param value Parameter value.
*/
RequestOptions setQueryParam(String name, boolean value);
/**
* Adds a query parameter to the request URL.
*
* @param name Parameter name.
* @param value Parameter value.
*/
RequestOptions setQueryParam(String name, int value);
/**
* Sets request timeout in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*
* @param timeout Request timeout in milliseconds.
*/
RequestOptions setTimeout(double timeout);
}
@@ -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 ScreenshotAnimations {
DISABLED,
ALLOW
}
@@ -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
}

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