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

Compare commits

...

69 Commits

Author SHA1 Message Date
Yury Semikhatsky 23ad37122a chore: set version to 1.19.0 (#814) 2022-02-15 16:06:10 -08:00
Yury Semikhatsky b448a1789a chore: roll 1.19.0 driver (#813) 2022-02-15 14:06:06 -08:00
Yury Semikhatsky 79b2c70513 fix: catch up with recent upstream changes (#812) 2022-02-15 08:39:27 -08:00
Max Schmitt d476d0a98c devops: trigger Java publish workflow on release (#811) 2022-02-14 22:44:03 +01:00
Yury Semikhatsky 2122c5690a fix: support multiple source dirs (#809) 2022-02-14 09:52:29 -08:00
Yury Semikhatsky 3119102b10 fix(assertions): include expected/actual values into error message (#808) 2022-02-11 13:02:27 -08:00
Yury Semikhatsky 838e7a40b3 feat: roll driver, support "has" option (#805) 2022-02-10 14:11:39 -08:00
Yury Semikhatsky ea6ede4670 fix: skip syntetic fields when converting options (#804) 2022-02-09 11:44:46 -08:00
Yury Semikhatsky 3cdefc2931 fix: normalize whitespaces in hasTitle (#803) 2022-02-09 09:54:46 -08:00
Yury Semikhatsky 119700a678 feat: add response param to route.fulfill (#797) 2022-01-28 08:38:24 -08:00
Yury Semikhatsky 17a4143a83 fix: throw if route is handled twice (#796) 2022-01-27 14:02:11 -08:00
Max Schmitt c03f4a9384 fix: CLI don't download browers automatically and set env accordingly (#790) 2022-01-24 12:54:49 -08:00
Andrey Lushnikov 85b671328e chore: roll to latest 1.18 (#785) 2022-01-19 16:42:07 -08:00
Yury Semikhatsky 11f898ca7f test: route.resume does not throw if page is closed (#781) 2022-01-19 13:19:33 -08:00
Yury Semikhatsky 2aef5c6742 fix: hide internal call from inspector log (#783) 2022-01-19 13:18:47 -08:00
Yury Semikhatsky 897d441c02 fix: include var name into tracing error message (#779) 2022-01-19 10:42:47 -08:00
Andrey Lushnikov f4c69faad3 chore: cut v1.18.0 (#777) 2022-01-19 08:42:53 -08:00
Andrey Lushnikov 6b30c0b3d2 fix: pass required env variables for new driver (#774)
Fixes #772
2022-01-19 05:15:37 -08:00
Yury Semikhatsky a006d51872 chore: merge assertions module into playwright (#773) 2022-01-18 12:22:15 -08:00
Yury Semikhatsky f411bf4194 chore: roll driver to 01/13/22 (#771) 2022-01-13 23:55:50 -08:00
Yury Semikhatsky 25a0927056 feat: roll driver, comoute count in util world (#769) 2022-01-10 13:20:20 -08:00
Yury Semikhatsky e9b379f5ed test: unroute predicate (#768) 2022-01-10 12:41:10 -08:00
Yury Semikhatsky 0c1d491c14 feat: roll driver, implement APIResponse assertions (#764) 2022-01-06 17:16:19 -08:00
Yury Semikhatsky b09b9aecfb feat: custom temp dir for driver via playwright.driver.tmpdir property (#763) 2022-01-06 14:36:39 -08:00
Yury Semikhatsky e926c1ae82 feat(tracing): collect sources for remote tracing (#755) 2021-12-20 12:20:57 -08:00
Yury Semikhatsky 86e91590cb feat: include source files in trace (#754) 2021-12-17 16:58:00 -08:00
Yury Semikhatsky 963afac983 feat: make --version return java package version (#748) 2021-12-16 14:38:15 -08:00
Yury Semikhatsky c230bed27e feat: roll driver, implement hasText locator option (#747) 2021-12-16 13:03:26 -08:00
Yury Semikhatsky a4348f250f feat: inclide Implementation-Version into manifest (#743) 2021-12-09 17:17:52 -08:00
Yury Semikhatsky 52d31a173e devops: run tests on Java 17 (#740) 2021-12-08 09:34:52 -08:00
Yury Semikhatsky cf534a0586 feat: roll to 1.18.0-alpha-nov-29-2021 (#725) 2021-11-30 15:05:05 -08:00
Yury Semikhatsky fa3bdebcbb chore: remove unused script (#724) 2021-11-30 12:21:29 -08:00
Bruno Borges 0467aa7d4a chore: No more need for jbang-catalog.json (#721) 2021-11-29 08:45:59 -08:00
codeboyzhou 8d84afccec fix(scripts): install_local_driver.sh can't download driver successfully (#722) 2021-11-29 08:41:12 -08:00
Yury Semikhatsky 7d1026ea9c feat: request API with shared RequestOptions and FormData (#718) 2021-11-19 17:35:59 -08:00
Yury Semikhatsky 0076b8f8a9 chore: bump version in examples (#716) 2021-11-19 08:58:07 -08:00
Yury Semikhatsky 4d379726e5 test: fix WARNING: sendResponseHeaders:... (#713) 2021-11-18 17:18:55 -08:00
Yury Semikhatsky b4100a4d68 chore: roll driver to 1.18.0-alpha-1637257604000 (#711) 2021-11-18 16:52:36 -08:00
Andrey Lushnikov 3b4d8dc955 chore: proper driver URL detection (#709)
Since Nov 16, 2021, we have the following conventions:

- Drivers published from tip-of-tree have an `-alpha` version and are
  stored at `/next` subfolder on Azure Storage.
- Drivers auto-published for each commit of the release branch have a `-beta` version and are
  stored at `/next` subfolder on Azure Storage.
- Drivers published due to a release might have `-rc` as part of the
  version, and are stored in root subfolder on Azure Storage.

We no longer have driver versions that include "next" as part of the
version. I kept it for backwards compatibility.
2021-11-17 18:32:01 -08:00
Yury Semikhatsky d71795801c chore: roll driver to 1.18.0-alpha-1637178126000 (#708) 2021-11-17 14:04:33 -08:00
Yury Semikhatsky 16c7b7f25e chore: use try resource in auth tests (#707) 2021-11-17 11:52:24 -08:00
Yury Semikhatsky f739ae28e8 chore: convert using reflection instead of Gson (#705) 2021-11-16 22:33:38 -08:00
Yury Semikhatsky 9f8ff0e7a1 feat: APIRequest & co (#704) 2021-11-15 17:30:09 -08:00
Yury Semikhatsky f782ca6339 chore: bump dev version to 1.18 (#703) 2021-11-15 15:10:23 -08:00
Yury Semikhatsky 90ccaa195f devops: clean old build before updating readme (#702) 2021-11-15 15:08:08 -08:00
Max Schmitt 4be749f045 devops: use main branch instead of master (#699) 2021-11-13 14:39:27 +01:00
Yury Semikhatsky f515d9f318 feat: frame locators, roll driver (#695) 2021-11-10 09:09:04 -08:00
Yury Semikhatsky 2f706012a7 feat: roll driver (#694) 2021-11-05 18:29:38 -07:00
Yury Semikhatsky 853b5062e7 tests: unflake wheel test, add more logs (#688) 2021-11-02 13:33:43 -07:00
Yury Semikhatsky 2d0d941e18 feat: support wait until commit, roll driver (#687) 2021-11-02 13:07:56 -07:00
Yury Semikhatsky 49a54d7ee4 fix(tests): do not use redirectTestOutputToFile (#685) 2021-11-01 13:08:19 -07:00
Yury Semikhatsky 44a85c1dc3 chore: roll driver (#681) 2021-11-01 11:31:09 -07:00
Yury Semikhatsky 5d7ee12f4a fix(route): do not wait for driver ack (#680) 2021-10-29 13:16:42 -07:00
Yury Semikhatsky a0416459e1 feat(assertions): support some regex flags, improve error messages (#676) 2021-10-28 18:37:15 -07:00
Yury Semikhatsky ddffc45e84 tests: use shorter timeout for in page evals (#675) 2021-10-28 16:51:58 -07:00
Yury Semikhatsky e85258908e fix(assertions): include property name into message (#674) 2021-10-28 15:43:57 -07:00
Max Schmitt c61d1da352 chore: drop support for Windows 32 bit (#673) 2021-10-28 08:26:50 -07:00
Yury Semikhatsky a60b0a9b78 fix(assertions): error message when not is used (#671) 2021-10-27 16:46:13 -07:00
Yury Semikhatsky 38bde7ad25 fix(assertions): set default timeout to 5s (#670) 2021-10-27 16:18:42 -07:00
Yury Semikhatsky a8e41b1ede fix(docker): add test-jar to the project dependencies (#668) 2021-10-27 15:03:57 -07:00
Yury Semikhatsky 45b141811b chore: extract common routines to base class (#666) 2021-10-26 17:53:09 -07:00
Yury Semikhatsky bd6ed7bc88 feat: locator assertions part 2 (#663) 2021-10-26 16:41:47 -07:00
Yury Semikhatsky d0e7ab1e58 feat: locator assertions (part 1) (#662) 2021-10-25 18:31:07 -07:00
Yury Semikhatsky 7ff7ee188b chore: disable interception when route.times==0 (#660) 2021-10-22 12:42:51 -07:00
Yury Semikhatsky d291a64e11 chore: driver-side waitForTimeout (#651) 2021-10-22 08:58:00 -07:00
Yury Semikhatsky 9f2b482084 chore: move common parts to parent pom, update module descriptions (#658) 2021-10-21 23:40:17 -07:00
Yury Semikhatsky b7319c629d feat: add web-first assertions for page (#657) 2021-10-21 18:22:00 -07:00
Yury Semikhatsky 38c5dc28a4 chore: bump development version (#656) 2021-10-21 17:54:52 -07:00
Andrey Lushnikov 1b9f7732fe chore: roll driver to 1.16 release (#653) 2021-10-21 00:00:39 -07:00
130 changed files with 7964 additions and 770 deletions
+3 -2
View File
@@ -1,9 +1,10 @@
name: Publish
on:
workflow_dispatch:
release:
types: [published]
push:
branches:
- master
- main
jobs:
build:
timeout-minutes: 30
+1 -1
View File
@@ -3,7 +3,7 @@ name: "devrelease:docker"
on:
push:
branches:
- master
- main
jobs:
publish-canary-docker:
name: "publish to DockerHub"
+55 -8
View File
@@ -2,11 +2,11 @@ name: Build & Test
on:
push:
branches:
- master
- main
- release-*
pull_request:
branches:
- master
- main
- release-*
jobs:
dev:
@@ -21,9 +21,10 @@ jobs:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: zulu
java-version: 8
- name: Cache Maven packages
uses: actions/cache@v2
with:
@@ -36,9 +37,14 @@ jobs:
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress
run: mvn test --no-transfer-progress --fail-at-end
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:
@@ -70,9 +76,10 @@ jobs:
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: zulu
java-version: 8
- name: Cache Maven packages
uses: actions/cache@v2
with:
@@ -85,7 +92,47 @@ jobs:
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress
run: mvn test --no-transfer-progress --fail-at-end
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.5.0
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: adopt
java-version: 17
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: m2-${{ hashFiles('**/pom.xml') }}
restore-keys: m2
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- 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
env:
BROWSER: ${{ matrix.browser }}
- 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
+5 -4
View File
@@ -2,17 +2,15 @@ name: Test CLI
on:
push:
branches:
- master
- main
- release-*
pull_request:
branches:
- master
- main
- release-*
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -28,3 +26,6 @@ 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
+2 -2
View File
@@ -5,7 +5,7 @@ on:
- '.github/workflows/test_docker.yml'
- 'Dockerfile*'
branches:
- master
- main
- release-*
pull_request:
paths:
@@ -14,7 +14,7 @@ on:
- scripts/CLI_VERSION
- '**/pom.xml'
branches:
- master
- main
- release-*
jobs:
test:
+2 -4
View File
@@ -2,14 +2,14 @@ name: Verify API
on:
push:
branches:
- master
- main
- release-*
paths:
- 'scripts/*'
- 'api-generator/*'
pull_request:
branches:
- master
- main
- release-*
paths:
- 'scripts/**'
@@ -17,8 +17,6 @@ on:
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
+4 -4
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->97.0.4666.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->100.0.4863.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: |
| Firefox <!-- GEN:firefox-version -->96.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
@@ -43,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.14.1</version>
<version>1.17.0</version>
</dependency>
```
@@ -179,7 +179,7 @@ You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.pl
## Contributing
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.
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.
## Is Playwright for Java ready?
+2 -17
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
<name>Playwright - Drivers For All Platforms</name>
<description>
This module includes playwright-cli binary and related utilities for all supported platforms.
This module includes Playwright driver and related utilities for all supported platforms.
It is intended to be used on the systems where Playwright driver is not preinstalled.
</description>
@@ -28,25 +28,10 @@
<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,15 +28,22 @@ public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private final Path driverTempDir;
DriverJar() throws IOException, URISyntaxException, InterruptedException {
driverTempDir = Files.createTempDirectory("playwright-java-");
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);
driverTempDir.toFile().deleteOnExit();
}
@Override
protected void initialize(Map<String, String> env) throws Exception {
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
extractDriverToTempDir();
installBrowsers(env);
if (installBrowsers)
installBrowsers(env);
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
@@ -48,13 +55,13 @@ public class DriverJar extends Driver {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
String cliFileName = super.cliFileName();
Path driver = driverTempDir.resolve(cliFileName);
Path driver = driverPath();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
throw new RuntimeException("Failed to find driver: " + 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();
@@ -131,7 +138,7 @@ public class DriverJar extends Driver {
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
if (name.contains("windows")) {
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
return "win32_x64";
}
if (name.contains("linux")) {
return "linux";
@@ -17,22 +17,30 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.impl.DriverJar;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestInstall {
@Test
void playwrightCliInstalled() throws Exception {
@BeforeEach
void clearSystemProperties() {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap());
System.clearProperty("playwright.driver.tmpdir");
}
@Test
void playwrightCliInstalled() throws Exception {
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
@@ -42,4 +50,11 @@ 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);
}
}
+2 -17
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>driver</artifactId>
<name>Playwright - Driver</name>
<description>
This module provides API for discovery and launching of playwright-cli binary.
This module provides API for discovery and launching of Playwright driver.
</description>
<build>
@@ -24,25 +24,10 @@
<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>
@@ -35,7 +35,7 @@ public abstract class Driver {
}
@Override
protected void initialize(Map<String, String> env) {
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
// no-op
}
@@ -45,24 +45,45 @@ public abstract class Driver {
}
}
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
if (instance == null) {
try {
instance = createDriver();
instance.initialize(env);
instance.initialize(env, installBrowsers);
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
}
String name = instance.cliFileName();
return instance.driverDir().resolve(name);
return instance.driverPath();
}
protected abstract void initialize(Map<String, String> env) throws Exception;
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
protected String cliFileName() {
return System.getProperty("os.name").toLowerCase().contains("windows") ?
public Path driverPath() {
String cliFileName = 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 {
+2 -2
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.19.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.15.0</version>
<version>1.17.0</version>
</dependency>
</dependencies>
<build>
-10
View File
@@ -1,10 +0,0 @@
{
"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": {}
}
+22 -11
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.16.0-SNAPSHOT</version>
<version>1.19.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -31,17 +31,7 @@
<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>
@@ -51,7 +41,24 @@
<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>
@@ -66,6 +73,10 @@
<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>
@@ -0,0 +1,184 @@
/*
* 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. Each Playwright browser context has a APIRequestContext instance
* attached which shares cookies with the page context. Its also possible to create a new APIRequestContext instance
* manually. For more information see <a href="https://playwright.dev/java/docs/class-apirequestcontext">here</a>.
*/
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);
}
@@ -0,0 +1,212 @@
/*
* 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. When used on {@code Page} or a {@code BrowserContext}, this API will automatically use
* the cookies from the corresponding {@code BrowserContext}. This means that if you log in using this API, your e2e test will be
* logged in and vice versa.
*/
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);
}
@@ -0,0 +1,65 @@
/*
* 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();
}
@@ -57,7 +57,7 @@ public interface Browser extends AutoCloseable {
class NewContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public Boolean acceptDownloads;
/**
@@ -69,6 +69,8 @@ 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;
@@ -205,7 +207,7 @@ public interface Browser extends AutoCloseable {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -220,6 +222,8 @@ 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) {
@@ -481,7 +485,7 @@ public interface Browser extends AutoCloseable {
}
class NewPageOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public Boolean acceptDownloads;
/**
@@ -493,6 +497,8 @@ 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;
@@ -629,7 +635,7 @@ public interface Browser extends AutoCloseable {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -644,6 +650,8 @@ 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) {
@@ -547,6 +547,10 @@ 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.
@@ -370,7 +370,7 @@ public interface BrowserType {
}
class LaunchPersistentContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public Boolean acceptDownloads;
/**
@@ -387,6 +387,8 @@ 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;
@@ -581,7 +583,7 @@ public interface BrowserType {
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
*/
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
@@ -604,6 +606,8 @@ 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) {
@@ -29,11 +29,13 @@ import static java.util.Arrays.asList;
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = new ProcessBuilder(driver.toString());
pb.command().addAll(asList(args));
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
pb.environment().put("PW_CLI_TARGET_LANG", "java");
Driver.setRequiredEnvironmentVariables(pb);
String version = Playwright.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
pb.inheritIO();
Process process = pb.start();
@@ -16,7 +16,6 @@
package com.microsoft.playwright;
import java.util.*;
/**
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
@@ -18,7 +18,6 @@ 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.
@@ -40,10 +39,6 @@ import java.util.*;
* // 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 {
/**
@@ -23,6 +23,8 @@ import java.util.*;
/**
* ElementHandle represents an in-page DOM element. ElementHandles can be created with the {@link Page#querySelector
* Page.querySelector()} method.
*
* <p> <strong>NOTE:</strong> The use of ElementHandle is discouraged, use {@code Locator} objects and web-first assertions instead.
* <pre>{@code
* ElementHandle hrefElement = page.querySelector("a");
* hrefElement.click();
@@ -34,10 +36,6 @@ import java.util.*;
* <p> ElementHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()} and {@link
* Page#evaluate Page.evaluate()} methods.
*
* <p> <strong>NOTE:</strong> In most cases, you would want to use the {@code Locator} object instead. You should only use {@code ElementHandle} if you want to
* retain a handle to a particular DOM Node that you intend to pass into {@link Page#evaluate Page.evaluate()} as an
* argument.
*
* <p> The difference between the {@code Locator} and ElementHandle is that the ElementHandle points to a particular element, while
* {@code Locator} captures the logic of how to retrieve an element.
*
@@ -18,7 +18,6 @@ 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.
@@ -813,6 +813,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -841,6 +842,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public NavigateOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -1215,6 +1217,47 @@ public interface Frame {
return this;
}
}
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. 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. 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. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
@@ -1455,6 +1498,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1475,6 +1519,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public SetContentOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -1886,6 +1931,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1933,6 +1979,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitForNavigationOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -2012,6 +2059,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -2032,6 +2080,7 @@ public interface Frame {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitForURLOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -2321,6 +2370,9 @@ public interface Frame {
/**
* Returns the return value of {@code expression}.
*
* <p> <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the frame and passes it as a first argument to
* {@code expression}. See <a href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If
* no elements match the selector, the method throws an error.
@@ -2348,6 +2400,9 @@ public interface Frame {
/**
* Returns the return value of {@code expression}.
*
* <p> <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the frame and passes it as a first argument to
* {@code expression}. See <a href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If
* no elements match the selector, the method throws an error.
@@ -2374,6 +2429,9 @@ public interface Frame {
/**
* Returns the return value of {@code expression}.
*
* <p> <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the frame and passes it as a first argument to
* {@code expression}. See <a href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If
* no elements match the selector, the method throws an error.
@@ -2399,6 +2457,9 @@ public interface Frame {
/**
* Returns the return value of {@code expression}.
*
* <p> <strong>NOTE:</strong> In most cases, {@link Locator#evaluateAll Locator.evaluateAll()}, other {@code Locator} helper methods and web-first
* assertions do a better job.
*
* <p> The method finds all elements matching the specified selector within the frame and passes an array of matched elements
* as a first argument to {@code expression}. See <a href="https://playwright.dev/java/docs/selectors/">Working with
* selectors</a> for more details.
@@ -2423,6 +2484,9 @@ public interface Frame {
/**
* Returns the return value of {@code expression}.
*
* <p> <strong>NOTE:</strong> In most cases, {@link Locator#evaluateAll Locator.evaluateAll()}, other {@code Locator} helper methods and web-first
* assertions do a better job.
*
* <p> The method finds all elements matching the specified selector within the frame and passes an array of matched elements
* as a first argument to {@code expression}. See <a href="https://playwright.dev/java/docs/selectors/">Working with
* selectors</a> for more details.
@@ -2467,7 +2531,7 @@ public interface Frame {
*
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link Frame#evaluate Frame.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = frame.querySelector("body");
* ElementHandle bodyHandle = frame.evaluate("document.body");
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -2502,7 +2566,7 @@ public interface Frame {
*
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link Frame#evaluate Frame.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = frame.querySelector("body");
* ElementHandle bodyHandle = frame.evaluate("document.body");
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -2645,6 +2709,19 @@ public interface Frame {
* }</pre>
*/
ElementHandle frameElement();
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = frame.frameLocator("#my-iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @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 element attribute value.
*
@@ -2919,7 +2996,18 @@ public interface Frame {
* @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);
default Locator locator(String selector) {
return locator(selector, null);
}
/**
* The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* @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 frame's name attribute as specified in the tag.
*
@@ -2989,6 +3077,8 @@ public interface Frame {
/**
* Returns the ElementHandle pointing to the frame element.
*
* <p> <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects and web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the frame. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If no elements match the
* selector, returns {@code null}.
@@ -3002,6 +3092,8 @@ public interface Frame {
/**
* Returns the ElementHandle pointing to the frame element.
*
* <p> <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects and web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the frame. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If no elements match the
* selector, returns {@code null}.
@@ -3013,6 +3105,8 @@ public interface Frame {
/**
* Returns the ElementHandles pointing to the frame elements.
*
* <p> <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects instead.
*
* <p> The method finds all elements matching the specified selector within the frame. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details. If no elements match the
* selector, returns empty array.
@@ -3883,6 +3977,9 @@ public interface Frame {
* Returns when element specified by selector satisfies {@code state} option. Returns {@code null} if waiting for {@code hidden} or
* {@code detached}.
*
* <p> <strong>NOTE:</strong> Playwright automatically waits for element to be ready before performing an action. Using {@code Locator} objects and
* web-first assertions make the code wait-for-selector-free.
*
* <p> Wait for the {@code selector} to satisfy {@code state} option (either appear/disappear from dom, or become visible/hidden). If at
* the moment of calling the method {@code selector} already satisfies the condition, the method will return immediately. If the
* selector doesn't satisfy the condition for the {@code timeout} milliseconds, the function will throw.
@@ -3918,6 +4015,9 @@ public interface Frame {
* Returns when element specified by selector satisfies {@code state} option. Returns {@code null} if waiting for {@code hidden} or
* {@code detached}.
*
* <p> <strong>NOTE:</strong> Playwright automatically waits for element to be ready before performing an action. Using {@code Locator} objects and
* web-first assertions make the code wait-for-selector-free.
*
* <p> Wait for the {@code selector} to satisfy {@code state} option (either appear/disappear from dom, or become visible/hidden). If at
* the moment of calling the method {@code selector} already satisfies the condition, the method will return immediately. If the
* selector doesn't satisfy the condition for the {@code timeout} milliseconds, the function will throw.
@@ -0,0 +1,129 @@
/*
* 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. 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. 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. 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.
*/
FrameLocator nth(int index);
}
@@ -17,7 +17,6 @@
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()},
@@ -19,49 +19,14 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
/**
* Locator represents a view to the element(s) on the page. It captures the logic sufficient to retrieve the element at any
* given moment. Locator can be created with the {@link Page#locator Page.locator()} method.
* <pre>{@code
* Locator locator = page.locator("text=Submit");
* locator.click();
* }</pre>
* Locators are the central piece of Playwright's auto-waiting and retry-ability. In a nutshell, locators represent a way
* to find element(s) on the page at any moment. Locator can be created with the {@link Page#locator Page.locator()}
* method.
*
* <p> The difference between the Locator and {@code ElementHandle} is that the latter points to a particular element, while Locator
* captures the logic of how to retrieve that element.
*
* <p> In the example below, handle points to a particular DOM element on page. If that element changes text or is used by
* React to render an entirely different component, handle is still pointing to that very DOM element. This can lead to
* unexpected behaviors.
* <pre>{@code
* ElementHandle handle = page.querySelector("text=Submit");
* handle.hover();
* handle.click();
* }</pre>
*
* <p> With the locator, every time the {@code element} is used, up-to-date DOM element is located in the page using the selector. So
* in the snippet below, underlying DOM element is going to be located twice.
* <pre>{@code
* Locator locator = page.locator("text=Submit");
* locator.hover();
* locator.click();
* }</pre>
*
* <p> **Strictness**
*
* <p> Locators are strict. This means that all operations on locators that imply some target DOM element will throw if more
* than one element matches given selector.
* <pre>{@code
* // Throws if there are several buttons in DOM:
* page.locator("button").click();
*
* // Works because we explicitly tell locator to pick the first element:
* page.locator("button").first().click();
*
* // Works because count knows what to do with multiple matches:
* page.locator("button").count();
* }</pre>
* <p> <a href="https://playwright.dev/java/docs/locators/">Learn more about locators</a>.
*/
public interface Locator {
class BoundingBoxOptions {
@@ -424,6 +389,107 @@ public interface Locator {
return this;
}
}
class DragToOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public Boolean force;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public Position targetPosition;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public DragToOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public DragToOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public DragToOptions setSourcePosition(double x, double y) {
return setSourcePosition(new Position(x, y));
}
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public DragToOptions setSourcePosition(Position sourcePosition) {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public DragToOptions setTargetPosition(double x, double y) {
return setTargetPosition(new Position(x, y));
}
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
*/
public DragToOptions setTargetPosition(Position targetPosition) {
this.targetPosition = targetPosition;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public DragToOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public DragToOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class ElementHandleOptions {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
@@ -797,6 +863,47 @@ public interface Locator {
return this;
}
}
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. 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. 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. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
@@ -1674,6 +1781,20 @@ public interface Locator {
* @param eventInit Optional event-specific initialization properties.
*/
void dispatchEvent(String type, Object eventInit, DispatchEventOptions options);
/**
*
*
* @param target Locator of the element to drag to.
*/
default void dragTo(Locator target) {
dragTo(target, null);
}
/**
*
*
* @param target Locator of the element to drag to.
*/
void dragTo(Locator target, DragToOptions options);
/**
* Resolves given locator to the first matching DOM element. If no elements matching the query are visible, waits for them
* up to a given timeout. If multiple elements match the selector, throws.
@@ -1898,6 +2019,18 @@ public interface Locator {
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus">focus</a> on the element.
*/
void focus(FocusOptions options);
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe:
* <pre>{@code
* Locator locator = page.frameLocator("iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @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 element attribute value.
*
@@ -2045,17 +2178,29 @@ public interface Locator {
*/
Locator last();
/**
* The method finds an element matching the specified selector in the {@code Locator}'s subtree. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more details.
* The method finds an element matching the specified selector in the {@code Locator}'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);
default Locator locator(String selector) {
return locator(selector, null);
}
/**
* The method finds an element matching the specified selector in the {@code Locator}'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 element.
*/
Locator nth(int index);
/**
* A page this locator belongs to.
*/
Page page();
/**
* Focuses the element, and then uses {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
*
@@ -17,7 +17,6 @@
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.
@@ -147,10 +147,6 @@ public interface Page extends AutoCloseable {
/**
* Emitted when attachment download started. User can access basic file operations on downloaded content via the passed
* {@code Download} instance.
*
* <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.
*/
void onDownload(Consumer<Download> handler);
/**
@@ -1147,6 +1143,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1167,6 +1164,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public GoBackOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -1188,6 +1186,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1208,6 +1207,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public GoForwardOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -1234,6 +1234,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1262,6 +1263,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public NavigateOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -1636,6 +1638,47 @@ public interface Page extends AutoCloseable {
return this;
}
}
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. 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. 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. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
class PdfOptions {
/**
* Display header and footer. Defaults to {@code false}.
@@ -1889,6 +1932,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -1909,6 +1953,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public ReloadOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -2195,6 +2240,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -2215,6 +2261,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public SetContentOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -2723,6 +2770,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -2770,6 +2818,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitForNavigationOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -2937,6 +2986,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitUntilState waitUntil;
@@ -2957,6 +3007,7 @@ public interface Page extends AutoCloseable {
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* <li> {@code "commit"} - consider operation to be finished when network response is received and the document started loading.</li>
* </ul>
*/
public WaitForURLOptions setWaitUntil(WaitUntilState waitUntil) {
@@ -3453,7 +3504,10 @@ public interface Page extends AutoCloseable {
*/
void emulateMedia(EmulateMediaOptions options);
/**
* The method finds an element matching the specified selector within the page and passes it as a first argument to
* <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the page and passes it as a first argument to
* {@code expression}. If no elements match the selector, the method throws an error. Returns the value of {@code expression}.
*
* <p> If {@code expression} returns a <a
@@ -3479,7 +3533,10 @@ public interface Page extends AutoCloseable {
return evalOnSelector(selector, expression, arg, null);
}
/**
* The method finds an element matching the specified selector within the page and passes it as a first argument to
* <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the page and passes it as a first argument to
* {@code expression}. If no elements match the selector, the method throws an error. Returns the value of {@code expression}.
*
* <p> If {@code expression} returns a <a
@@ -3504,7 +3561,10 @@ public interface Page extends AutoCloseable {
return evalOnSelector(selector, expression, null);
}
/**
* The method finds an element matching the specified selector within the page and passes it as a first argument to
* <strong>NOTE:</strong> This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. Use
* {@link Locator#evaluate Locator.evaluate()}, other {@code Locator} helper methods or web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the page and passes it as a first argument to
* {@code expression}. If no elements match the selector, the method throws an error. Returns the value of {@code expression}.
*
* <p> If {@code expression} returns a <a
@@ -3528,7 +3588,10 @@ public interface Page extends AutoCloseable {
*/
Object evalOnSelector(String selector, String expression, Object arg, EvalOnSelectorOptions options);
/**
* The method finds all elements matching the specified selector within the page and passes an array of matched elements as
* <strong>NOTE:</strong> In most cases, {@link Locator#evaluateAll Locator.evaluateAll()}, other {@code Locator} helper methods and web-first
* assertions do a better job.
*
* <p> The method finds all elements matching the specified selector within the page and passes an array of matched elements as
* a first argument to {@code expression}. Returns the result of {@code expression} invocation.
*
* <p> If {@code expression} returns a <a
@@ -3549,7 +3612,10 @@ public interface Page extends AutoCloseable {
return evalOnSelectorAll(selector, expression, null);
}
/**
* The method finds all elements matching the specified selector within the page and passes an array of matched elements as
* <strong>NOTE:</strong> In most cases, {@link Locator#evaluateAll Locator.evaluateAll()}, other {@code Locator} helper methods and web-first
* assertions do a better job.
*
* <p> The method finds all elements matching the specified selector within the page and passes an array of matched elements as
* a first argument to {@code expression}. Returns the result of {@code expression} invocation.
*
* <p> If {@code expression} returns a <a
@@ -3594,7 +3660,7 @@ public interface Page extends AutoCloseable {
*
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link Page#evaluate Page.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = page.querySelector("body");
* ElementHandle bodyHandle = page.evaluate("document.body");
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -3633,7 +3699,7 @@ public interface Page extends AutoCloseable {
*
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link Page#evaluate Page.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = page.querySelector("body");
* ElementHandle bodyHandle = page.evaluate("document.body");
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -3979,6 +4045,19 @@ public interface Page extends AutoCloseable {
* @param url A glob pattern, regex pattern or predicate receiving frame's {@code url} as a [URL] object.
*/
Frame frameByUrl(Predicate<String> url);
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe. Following snippet locates element with text "Submit" in the iframe with id {@code my-frame}, like {@code <iframe
* id="my-frame">}:
* <pre>{@code
* Locator locator = page.frameLocator("#my-iframe").locator("text=Submit");
* locator.click();
* }</pre>
*
* @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);
/**
* An array of all frames attached to the page.
*/
@@ -4304,7 +4383,20 @@ public interface Page extends AutoCloseable {
* @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);
default Locator locator(String selector) {
return locator(selector, null);
}
/**
* The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* <p> Shortcut for main frame's {@link Frame#locator Frame.locator()}.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
* selectors</a> for more details.
*/
Locator locator(String selector, LocatorOptions options);
/**
* The page's main frame. Page is guaranteed to have a main frame which persists during navigations.
*/
@@ -4508,9 +4600,10 @@ public interface Page extends AutoCloseable {
*/
void press(String selector, String key, PressOptions options);
/**
* The method finds an element matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code null}. To wait for an element on the page, use {@link Page#waitForSelector
* Page.waitForSelector()}.
* <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects and web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code null}. To wait for an element on the page, use {@link Locator#waitFor Locator.waitFor()}.
*
* <p> Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}.
*
@@ -4521,9 +4614,10 @@ public interface Page extends AutoCloseable {
return querySelector(selector, null);
}
/**
* The method finds an element matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code null}. To wait for an element on the page, use {@link Page#waitForSelector
* Page.waitForSelector()}.
* <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects and web-first assertions instead.
*
* <p> The method finds an element matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code null}. To wait for an element on the page, use {@link Locator#waitFor Locator.waitFor()}.
*
* <p> Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}.
*
@@ -4532,7 +4626,9 @@ public interface Page extends AutoCloseable {
*/
ElementHandle querySelector(String selector, QuerySelectorOptions options);
/**
* The method finds all elements matching the specified selector within the page. If no elements match the selector, the
* <strong>NOTE:</strong> The use of {@code ElementHandle} is discouraged, use {@code Locator} objects and web-first assertions instead.
*
* <p> The method finds all elements matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code []}.
*
* <p> Shortcut for main frame's {@link Frame#querySelectorAll Frame.querySelectorAll()}.
@@ -4542,17 +4638,21 @@ public interface Page extends AutoCloseable {
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect.
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
*/
default Response reload() {
return reload(null);
}
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect.
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
*/
Response reload(ReloadOptions options);
/**
* API testing helper associated with this page. Requests made with this API will use page cookies.
*/
APIRequestContext request();
/**
* Routing provides the capability to modify network requests that are made by a page.
*
@@ -6316,6 +6416,9 @@ public interface Page extends AutoCloseable {
* Returns when element specified by selector satisfies {@code state} option. Returns {@code null} if waiting for {@code hidden} or
* {@code detached}.
*
* <p> <strong>NOTE:</strong> Playwright automatically waits for element to be ready before performing an action. Using {@code Locator} objects and
* web-first assertions make the code wait-for-selector-free.
*
* <p> Wait for the {@code selector} to satisfy {@code state} option (either appear/disappear from dom, or become visible/hidden). If at
* the moment of calling the method {@code selector} already satisfies the condition, the method will return immediately. If the
* selector doesn't satisfy the condition for the {@code timeout} milliseconds, the function will throw.
@@ -6351,6 +6454,9 @@ public interface Page extends AutoCloseable {
* Returns when element specified by selector satisfies {@code state} option. Returns {@code null} if waiting for {@code hidden} or
* {@code detached}.
*
* <p> <strong>NOTE:</strong> Playwright automatically waits for element to be ready before performing an action. Using {@code Locator} objects and
* web-first assertions make the code wait-for-selector-free.
*
* <p> Wait for the {@code selector} to satisfy {@code state} option (either appear/disappear from dom, or become visible/hidden). If at
* the moment of calling the method {@code selector} already satisfies the condition, the method will return immediately. If the
* selector doesn't satisfy the condition for the {@code timeout} milliseconds, the function will throw.
@@ -64,6 +64,10 @@ 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.
@@ -16,7 +16,6 @@
package com.microsoft.playwright;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
@@ -101,6 +100,11 @@ 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}.
*/
@@ -142,6 +146,14 @@ 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}.
*/
@@ -17,7 +17,6 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* Selectors can be used to install custom selector engines. See <a
@@ -62,11 +61,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
@@ -97,11 +96,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
@@ -130,11 +129,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
@@ -165,11 +164,11 @@ public interface Selectors {
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
@@ -16,7 +16,6 @@
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,7 +17,6 @@
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
@@ -48,9 +47,22 @@ public interface Tracing {
*/
public Boolean screenshots;
/**
* Whether to capture DOM snapshot on every action.
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
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.
*/
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
@@ -68,12 +80,45 @@ public interface Tracing {
return this;
}
/**
* Whether to capture DOM snapshot on every action.
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
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.
*/
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 {
/**
@@ -157,7 +202,34 @@ public interface Tracing {
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
void startChunk();
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.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")));
*
* 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);
/**
* Stop tracing.
*/
@@ -17,7 +17,6 @@
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,7 +16,6 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -16,7 +16,6 @@
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,7 +16,6 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
@@ -0,0 +1,56 @@
/*
* 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();
}
@@ -0,0 +1,158 @@
/*
* 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.click("#login");
* 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 substring 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 substring 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 substring 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 substring or RegExp.
*/
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
}
@@ -0,0 +1,90 @@
/*
* 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;
/**
* The {@code PlaywrightAssertions} class provides convenience methods for creating assertions that will wait 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.click("#submit-button");
* 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);
}
}
@@ -0,0 +1,199 @@
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;
}
}
@@ -0,0 +1,53 @@
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;
}
}
@@ -0,0 +1,59 @@
/*
* 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);
}
}
@@ -0,0 +1,124 @@
/*
* 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());
}
}
@@ -0,0 +1,94 @@
/*
* 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;
}
}
@@ -30,10 +30,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -47,6 +44,7 @@ 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;
@@ -74,7 +72,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = new TracingImpl(this);
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 setBaseUrl(String spec) {
@@ -172,7 +172,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : asList(url));
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl() {
@@ -329,6 +329,11 @@ 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(this.baseUrl, url), handler, options);
@@ -424,7 +429,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public Tracing tracing() {
public TracingImpl tracing() {
return tracing;
}
@@ -457,14 +462,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
maybeDisableNetworkInterception();
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
void handleRoute(Route route) {
boolean handled = routes.handle(route);
if (handled) {
maybeDisableNetworkInterception();
} else {
route.resume();
}
}
void pause() {
sendMessage("pause");
}
@@ -473,10 +491,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
route.resume();
}
handleRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page);
@@ -31,7 +31,7 @@ import java.util.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
@@ -40,6 +40,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
LocalUtils localUtils;
enum EventType {
DISCONNECTED,
@@ -171,6 +172,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
contexts.add(context);
return context;
}
@@ -209,7 +211,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
PageImpl page = context.newPage();
page.ownedContext = context;
context.ownerPage = page;
@@ -30,6 +30,8 @@ import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -45,7 +47,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params);
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.localUtils = localUtils;
return browser;
}
@Override
@@ -76,6 +80,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isRemote = true;
browser.isConnectedOverWebSocket = true;
browser.localUtils = localUtils;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
@@ -109,6 +114,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
browser.localUtils = localUtils;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
@@ -174,6 +180,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
return context;
}
@@ -16,23 +16,17 @@
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 {
@@ -62,7 +56,7 @@ public class Connection {
private final Map<String, ChannelOwner> objects = new HashMap<>();
private final Root root;
private int lastId = 0;
private final Path srcDir;
private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName;
private static final boolean isLogging;
@@ -90,15 +84,11 @@ public class Connection {
}
this.transport = transport;
root = new Root(this);
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 + "'");
}
}
stackTraceCollector = StackTraceCollector.createFromEnv();
}
boolean isCollectingStacks() {
return stackTraceCollector != null;
}
String setApiName(String name) {
@@ -119,45 +109,6 @@ 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<>();
@@ -168,11 +119,15 @@ public class Connection {
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
if (srcDir != null) {
metadata.add("stack", currentStackTrace());
}
if (apiName != null) {
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
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);
@@ -301,9 +256,9 @@ public class Connection {
case "ElementHandle":
result = new ElementHandleImpl(parent, type, guid, initializer);
break;
case "FetchRequest":
case "APIRequestContext":
// Create fake object as this API is experimental an only exposed in Node.js.
result = new ChannelOwner(parent, type, guid, initializer);
result = new APIRequestContextImpl(parent, type, guid, initializer);
break;
case "Frame":
result = new FrameImpl(parent, type, guid, initializer);
@@ -314,6 +269,9 @@ public class Connection {
case "JsonPipe":
result = new JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
result = new LocalUtils(parent, type, guid, initializer);
break;
case "Page":
result = new PageImpl(parent, type, guid, initializer);
break;
@@ -335,6 +293,9 @@ 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;
@@ -20,7 +20,6 @@ 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;
@@ -34,7 +33,7 @@ import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -435,9 +434,9 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setChecked(boolean checked, SetCheckedOptions options) {
if (checked) {
check(convertViaJson(options, CheckOptions.class));
check(convertType(options, CheckOptions.class));
} else {
uncheck(convertViaJson(options, UncheckOptions.class));
uncheck(convertType(options, UncheckOptions.class));
}
}
@@ -23,7 +23,7 @@ import com.microsoft.playwright.options.FilePayload;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.convertType;
class FileChooserImpl implements FileChooser {
private final PageImpl page;
@@ -69,6 +69,6 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -0,0 +1,58 @@
/*
* 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.options.LoadState.*;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.options.WaitUntilState.*;
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<LoadState> loadStates = new HashSet<>();
private final Set<WaitUntilState> loadStates = new HashSet<>();
enum InternalEventType { NAVIGATED, LOADSTATE }
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
@@ -61,11 +61,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
}
private static LoadState loadStateFromProtocol(String value) {
private static WaitUntilState 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);
}
}
@@ -357,6 +358,11 @@ 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());
@@ -560,8 +566,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Locator locator(String selector) {
return new LocatorImpl(this, selector);
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(this, selector, convertType(options, Locator.LocatorOptions.class));
}
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
@@ -580,7 +586,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Page page() {
public PageImpl page() {
return page;
}
@@ -650,9 +656,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
checkImpl(selector, convertViaJson(options, CheckOptions.class));
checkImpl(selector, convertType(options, CheckOptions.class));
} else {
uncheckImpl(selector, convertViaJson(options, UncheckOptions.class));
uncheckImpl(selector, convertType(options, UncheckOptions.class));
}
}
@@ -801,6 +807,10 @@ 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();
}
@@ -815,11 +825,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
runUntil(() -> {}, new WaitableRace<>(waitables));
}
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
private final LoadState expectedState;
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
private final WaitUntilState expectedState;
private boolean isDone;
WaitForLoadStateHelper(LoadState state) {
WaitForLoadStateHelper(WaitUntilState state) {
expectedState = state;
isDone = loadStates.contains(state);
if (!isDone) {
@@ -828,7 +838,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public void accept(LoadState state) {
public void accept(WaitUntilState state) {
if (expectedState.equals(state)) {
isDone = true;
dispose();
@@ -851,13 +861,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
private final UrlMatcher matcher;
private final LoadState expectedLoadState;
private final WaitUntilState expectedLoadState;
private WaitForLoadStateHelper loadStateHelper;
private RequestImpl request;
private RuntimeException exception;
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
this.matcher = matcher;
this.expectedLoadState = expectedLoadState;
internalListeners.add(InternalEventType.NAVIGATED, this);
@@ -935,7 +945,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
@@ -972,13 +982,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void waitForTimeoutImpl(double timeout) {
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
@Override
public Void get() {
// Override to not throw.
return null;
}
});
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("waitForTimeout", params);
}
@Override
@@ -1005,18 +1011,24 @@ public class FrameImpl extends ChannelOwner implements Frame {
options = new WaitForURLOptions();
}
if (matcher.test(url())) {
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
convertViaJson(options, WaitForLoadStateOptions.class));
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
return;
}
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
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();
}
protected void handleEvent(String event, JsonObject params) {
if ("loadstate".equals(event)) {
JsonElement add = params.get("add");
if (add != null) {
LoadState state = loadStateFromProtocol(add.getAsString());
WaitUntilState state = loadStateFromProtocol(add.getAsString());
loadStates.add(state);
internalListeners.notify(InternalEventType.LOADSTATE, state);
}
@@ -0,0 +1,57 @@
/*
* 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);
}
}
@@ -16,28 +16,20 @@
package com.microsoft.playwright.impl;
import java.util.function.Function;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
class WaitableAdapter<F, T> implements Waitable<T> {
private final Waitable<F> waitable;
private final Function<F, T> transformation;
import java.nio.file.Path;
WaitableAdapter(Waitable<F> waitable, Function<F, T> transformation) {
this.waitable = waitable;
this.transformation = transformation;
}
@Override
public boolean isDone() {
return waitable.isDone();
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public T get() {
return transformation.apply(waitable.get());
}
@Override
public void dispose() {
waitable.dispose();
void zip(Path zipFile, JsonArray entries) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
params.add("entries", entries);
sendMessage("zip", params);
}
}
@@ -0,0 +1,308 @@
/*
* 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.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.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.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.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.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.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);
// 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.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.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 isChecked(IsCheckedOptions options) {
expectTrue("to.be.checked", "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);
}
}
@@ -1,9 +1,9 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Locator;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
@@ -12,20 +12,46 @@ import com.microsoft.playwright.options.WaitForSelectorState;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
public LocatorImpl(FrameImpl frame, String selector) {
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
if (options != null) {
if (options.hasText != null) {
if (options.hasText instanceof Pattern) {
Pattern pattern = (Pattern) options.hasText;
selector += " >> :scope:text-matches(" + escapeWithQuotes(pattern.pattern()) + ", \"" + toJsRegexFlags(pattern) + "\")";
} else if (options.hasText instanceof String) {
String text = (String) options.hasText;
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
}
}
if (options.has != null) {
LocatorImpl has = (LocatorImpl) options.has;
if (has.frame != frame) {
throw new PlaywrightException("Inner 'has' locator must belong to the same frame.");
}
selector += " >> has=" + gson().toJson(has.selector);
}
}
this.selector = selector;
}
private static String escapeWithQuotes(String text) {
return gson().toJson(text);
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertViaJson(options, ElementHandleOptions.class);
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
@@ -63,7 +89,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new CheckOptions();
}
frame.check(selector, convertViaJson(options, Frame.CheckOptions.class).setStrict(true));
frame.check(selector, convertType(options, Frame.CheckOptions.class).setStrict(true));
}
@Override
@@ -71,12 +97,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ClickOptions();
}
frame.click(selector, convertViaJson(options, Frame.ClickOptions.class).setStrict(true));
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
}
@Override
public int count() {
return ((Number) evaluateAll("ee => ee.length")).intValue();
return frame.queryCount(selector);
}
@Override
@@ -84,7 +110,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new DblclickOptions();
}
frame.dblclick(selector, convertViaJson(options, Frame.DblclickOptions.class).setStrict(true));
frame.dblclick(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true));
}
@Override
@@ -92,7 +118,17 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new DispatchEventOptions();
}
frame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class).setStrict(true));
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);
}
@Override
@@ -100,7 +136,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ElementHandleOptions();
}
Frame.WaitForSelectorOptions frameOptions = convertViaJson(options, Frame.WaitForSelectorOptions.class);
Frame.WaitForSelectorOptions frameOptions = convertType(options, Frame.WaitForSelectorOptions.class);
frameOptions.setStrict(true);
frameOptions.setState(WaitForSelectorState.ATTACHED);
return frame.waitForSelector(selector, frameOptions);
@@ -131,12 +167,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new FillOptions();
}
frame.fill(selector, value, convertViaJson(options, Frame.FillOptions.class).setStrict(true));
frame.fill(selector, value, convertType(options, Frame.FillOptions.class).setStrict(true));
}
@Override
public Locator first() {
return new LocatorImpl(frame, selector + " >> nth=0");
return new LocatorImpl(frame, selector + " >> nth=0", null);
}
@Override
@@ -144,7 +180,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new FocusOptions();
}
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
frame.focus(selector, convertType(options, Frame.FocusOptions.class).setStrict(true));
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, this.selector + " >> " + selector);
}
@Override
@@ -152,7 +193,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new GetAttributeOptions();
}
return frame.getAttribute(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class).setStrict(true));
return frame.getAttribute(selector, name, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
}
@Override
@@ -160,7 +201,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new HoverOptions();
}
frame.hover(selector, convertViaJson(options, Frame.HoverOptions.class).setStrict(true));
frame.hover(selector, convertType(options, Frame.HoverOptions.class).setStrict(true));
}
@Override
@@ -168,7 +209,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InnerHTMLOptions();
}
return frame.innerHTML(selector, convertViaJson(options, Frame.InnerHTMLOptions.class).setStrict(true));
return frame.innerHTML(selector, convertType(options, Frame.InnerHTMLOptions.class).setStrict(true));
}
@Override
@@ -176,7 +217,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InnerTextOptions();
}
return frame.innerText(selector, convertViaJson(options, Frame.InnerTextOptions.class).setStrict(true));
return frame.innerText(selector, convertType(options, Frame.InnerTextOptions.class).setStrict(true));
}
@Override
@@ -184,7 +225,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new InputValueOptions();
}
return frame.inputValue(selector, convertViaJson(options, Frame.InputValueOptions.class).setStrict(true));
return frame.inputValue(selector, convertType(options, Frame.InputValueOptions.class).setStrict(true));
}
@Override
@@ -192,7 +233,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsCheckedOptions();
}
return frame.isChecked(selector, convertViaJson(options, Frame.IsCheckedOptions.class).setStrict(true));
return frame.isChecked(selector, convertType(options, Frame.IsCheckedOptions.class).setStrict(true));
}
@Override
@@ -200,7 +241,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsDisabledOptions();
}
return frame.isDisabled(selector, convertViaJson(options, Frame.IsDisabledOptions.class).setStrict(true));
return frame.isDisabled(selector, convertType(options, Frame.IsDisabledOptions.class).setStrict(true));
}
@Override
@@ -208,7 +249,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsEditableOptions();
}
return frame.isEditable(selector, convertViaJson(options, Frame.IsEditableOptions.class).setStrict(true));
return frame.isEditable(selector, convertType(options, Frame.IsEditableOptions.class).setStrict(true));
}
@Override
@@ -216,7 +257,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsEnabledOptions();
}
return frame.isEnabled(selector, convertViaJson(options, Frame.IsEnabledOptions.class).setStrict(true));
return frame.isEnabled(selector, convertType(options, Frame.IsEnabledOptions.class).setStrict(true));
}
@Override
@@ -224,7 +265,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsHiddenOptions();
}
return frame.isHidden(selector, convertViaJson(options, Frame.IsHiddenOptions.class).setStrict(true));
return frame.isHidden(selector, convertType(options, Frame.IsHiddenOptions.class).setStrict(true));
}
@Override
@@ -232,22 +273,27 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new IsVisibleOptions();
}
return frame.isVisible(selector, convertViaJson(options, Frame.IsVisibleOptions.class).setStrict(true));
return frame.isVisible(selector, convertType(options, Frame.IsVisibleOptions.class).setStrict(true));
}
@Override
public Locator last() {
return new LocatorImpl(frame, selector + " >> nth=-1");
return new LocatorImpl(frame, selector + " >> nth=-1", null);
}
@Override
public Locator locator(String selector) {
return new LocatorImpl(frame, this.selector + " >> " + selector);
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(frame, this.selector + " >> " + selector, options);
}
@Override
public Locator nth(int index) {
return new LocatorImpl(frame, selector + " >> nth=" + index);
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
}
@Override
public Page page() {
return frame.page();
}
@Override
@@ -255,12 +301,12 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new PressOptions();
}
frame.press(selector, key, convertViaJson(options, Frame.PressOptions.class).setStrict(true));
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertViaJson(options, ElementHandle.ScreenshotOptions.class));
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
}
@Override
@@ -268,7 +314,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o);
return null;
}, convertViaJson(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}
@Override
@@ -276,7 +322,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -284,7 +330,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -292,7 +338,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -300,7 +346,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -308,7 +354,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -316,7 +362,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
@@ -324,7 +370,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.selectText(o);
return null;
}, convertViaJson(options, ElementHandle.SelectTextOptions.class));
}, convertType(options, ElementHandle.SelectTextOptions.class));
}
@Override
@@ -332,7 +378,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetCheckedOptions();
}
frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class).setStrict(true));
frame.setChecked(selector, checked, convertType(options, Frame.SetCheckedOptions.class).setStrict(true));
}
@Override
@@ -340,7 +386,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -348,7 +394,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -356,7 +402,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -364,7 +410,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
@@ -372,7 +418,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TapOptions();
}
frame.tap(selector, convertViaJson(options, Frame.TapOptions.class).setStrict(true));
frame.tap(selector, convertType(options, Frame.TapOptions.class).setStrict(true));
}
@Override
@@ -380,7 +426,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TextContentOptions();
}
return frame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class).setStrict(true));
return frame.textContent(selector, convertType(options, Frame.TextContentOptions.class).setStrict(true));
}
@Override
@@ -388,7 +434,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new TypeOptions();
}
frame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class).setStrict(true));
frame.type(selector, text, convertType(options, Frame.TypeOptions.class).setStrict(true));
}
@Override
@@ -396,7 +442,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new UncheckOptions();
}
frame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class).setStrict(true));
frame.uncheck(selector, convertType(options, Frame.UncheckOptions.class).setStrict(true));
}
@Override
@@ -408,11 +454,27 @@ class LocatorImpl implements Locator {
}
private void waitForImpl(WaitForOptions options) {
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(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));
}
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;
}
}
@@ -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.convertViaJson;
import static com.microsoft.playwright.impl.Utils.convertType;
class MouseImpl implements Mouse {
private final ChannelOwner page;
@@ -54,7 +54,7 @@ class MouseImpl implements Mouse {
if (options == null) {
clickOptions = new ClickOptions();
} else {
clickOptions = convertViaJson(options, ClickOptions.class);
clickOptions = convertType(options, ClickOptions.class);
}
clickOptions.clickCount = 2;
click(x, y, clickOptions);
@@ -0,0 +1,74 @@
/*
* 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,10 +28,10 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.convertType;
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;
@@ -170,7 +170,9 @@ public class PageImpl extends ChannelOwner implements Page {
try {
bindingCall.call(binding);
} catch (RuntimeException e) {
e.printStackTrace();
if (!isSafeCloseError(e.getMessage())) {
logWithTimestamp(e.getMessage());
}
}
}
} else if ("load".equals(event)) {
@@ -198,11 +200,10 @@ public class PageImpl extends ChannelOwner implements Page {
} else if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
handled = browserContext.routes.handle(route);
}
if (!handled) {
route.resume();
if (handled) {
maybeDisableNetworkInterception();
} else {
browserContext.handleRoute(route);
}
} else if ("video".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
@@ -557,7 +558,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
selector, convertViaJson(options, Frame.QuerySelectorOptions.class)));
selector, convertType(options, Frame.QuerySelectorOptions.class)));
}
@Override
@@ -568,7 +569,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, convertViaJson(options, Frame.EvalOnSelectorOptions.class)));
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class)));
}
@Override
@@ -602,13 +603,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options) {
return withLogging("Page.addScriptTag",
() -> mainFrame.addScriptTagImpl(convertViaJson(options, Frame.AddScriptTagOptions.class)));
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
}
@Override
public ElementHandle addStyleTag(AddStyleTagOptions options) {
return withLogging("Page.addStyleTag",
() -> mainFrame.addStyleTagImpl(convertViaJson(options, Frame.AddStyleTagOptions.class)));
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
}
@Override
@@ -619,13 +620,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void check(String selector, CheckOptions options) {
withLogging("Page.check",
() -> mainFrame.checkImpl(selector, convertViaJson(options, Frame.CheckOptions.class)));
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
}
@Override
public void click(String selector, ClickOptions options) {
withLogging("Page.click",
() -> mainFrame.clickImpl(selector, convertViaJson(options, Frame.ClickOptions.class)));
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
}
@Override
@@ -641,13 +642,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void dblclick(String selector, DblclickOptions options) {
withLogging("Page.dblclick",
() -> mainFrame.dblclickImpl(selector, convertViaJson(options, Frame.DblclickOptions.class)));
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
}
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Page.dispatchEvent",
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class)));
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
}
@Override
@@ -704,13 +705,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, convertViaJson(options, Frame.FillOptions.class)));
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
}
@Override
public void focus(String selector, FocusOptions options) {
withLogging("Page.focus",
() -> mainFrame.focusImpl(selector, convertViaJson(options, Frame.FocusOptions.class)));
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
}
@Override
@@ -738,6 +739,11 @@ 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())) {
@@ -755,7 +761,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, convertViaJson(options, Frame.GetAttributeOptions.class)));
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
}
@Override
@@ -794,44 +800,41 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () ->
mainFrame.navigateImpl(url, convertViaJson(options, Frame.NavigateOptions.class)));
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
}
@Override
public void hover(String selector, HoverOptions options) {
withLogging("Page.hover", () ->
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class)));
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Page.dragAndDrop", () ->
mainFrame.dragAndDropImpl(source, target, convertViaJson(options, Frame.DragAndDropOptions.class)));
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class)));
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Page.innerHTML",
() -> mainFrame.innerHTMLImpl(selector, convertViaJson(options, Frame.InnerHTMLOptions.class)));
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
}
@Override
public String innerText(String selector, InnerTextOptions options) {
return withLogging("Page.innerText",
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Page.inputValue",
() -> mainFrame.inputValueImpl(selector, convertViaJson(options, Frame.InputValueOptions.class)));
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked",
() -> mainFrame.isCheckedImpl(selector, convertViaJson(options, Frame.IsCheckedOptions.class)));
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
}
@Override
@@ -842,31 +845,31 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled",
() -> mainFrame.isDisabledImpl(selector, convertViaJson(options, Frame.IsDisabledOptions.class)));
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable",
() -> mainFrame.isEditableImpl(selector, convertViaJson(options, Frame.IsEditableOptions.class)));
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled",
() -> mainFrame.isEnabledImpl(selector, convertViaJson(options, Frame.IsEnabledOptions.class)));
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden",
() -> mainFrame.isHiddenImpl(selector, convertViaJson(options, Frame.IsHiddenOptions.class)));
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible",
() -> mainFrame.isVisibleImpl(selector, convertViaJson(options, Frame.IsVisibleOptions.class)));
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
}
@Override
@@ -875,8 +878,8 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Locator locator(String selector) {
return mainFrame.locator(selector);
public Locator locator(String selector, LocatorOptions options) {
return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class));
}
@Override
@@ -929,7 +932,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, convertViaJson(options, Frame.PressOptions.class)));
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
}
@Override
@@ -937,6 +940,11 @@ 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();
@@ -1040,25 +1048,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, convertViaJson(options, Frame.SelectOptionOptions.class)));
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Page.setChecked",
() -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class)));
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Page.setContent",
() -> mainFrame.setContentImpl(html, convertViaJson(options, Frame.SetContentOptions.class)));
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
}
@Override
@@ -1105,7 +1113,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, convertViaJson(options, Frame.SetInputFilesOptions.class)));
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
}
@Override
@@ -1116,7 +1124,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, convertViaJson(options, Frame.SetInputFilesOptions.class)));
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
}
@Override
@@ -1132,13 +1140,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void tap(String selector, TapOptions options) {
withLogging("Page.tap",
() -> mainFrame.tapImpl(selector, convertViaJson(options, Frame.TapOptions.class)));
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
}
@Override
public String textContent(String selector, TextContentOptions options) {
return withLogging("Page.textContent",
() -> mainFrame.textContentImpl(selector, convertViaJson(options, Frame.TextContentOptions.class)));
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
}
@Override
@@ -1154,13 +1162,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, convertViaJson(options, Frame.TypeOptions.class)));
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
}
@Override
public void uncheck(String selector, UncheckOptions options) {
withLogging("Page.uncheck",
() -> mainFrame.uncheckImpl(selector, convertViaJson(options, Frame.UncheckOptions.class)));
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
}
@Override
@@ -1181,14 +1189,18 @@ public class PageImpl extends ChannelOwner implements Page {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("Page.unroute", () -> {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
maybeDisableNetworkInterception();
});
}
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();
@@ -1229,13 +1241,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, convertViaJson(options, Frame.WaitForFunctionOptions.class)));
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
}
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Page.waitForLoadState", () -> {
mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class));
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class));
return null;
});
}
@@ -1369,7 +1381,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Page.waitForSelector",
() -> mainFrame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class)));
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
}
@Override
@@ -1393,7 +1405,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertType(options, Frame.WaitForURLOptions.class)));
}
@Override
@@ -17,6 +17,7 @@
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;
@@ -36,10 +37,11 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
if (options != null && options.env != null) {
env = options.env;
}
Path driver = Driver.ensureDriverInstalled(env);
Path driver = Driver.ensureDriverInstalled(env, true);
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.environment().putAll(env);
Driver.setRequiredEnvironmentVariables(pb);
Process p = pb.start();
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = connection.initializePlaywright();
@@ -55,7 +57,9 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private SharedSelectors sharedSelectors;;
private final APIRequestImpl apiRequest;
private final LocalUtils localUtils;
private SharedSelectors sharedSelectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -64,12 +68,17 @@ 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;
}
@@ -90,6 +99,11 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
return firefox;
}
@Override
public APIRequest request() {
return apiRequest;
}
@Override
public BrowserTypeImpl webkit() {
return webkit;
@@ -18,18 +18,12 @@
package com.microsoft.playwright.impl;
class Binary {
}
import java.util.List;
class Channel {
String guid;
}
class Metadata{
String stack;
}
class SerializedValue{
Number n;
Boolean b;
@@ -51,45 +45,11 @@ class SerializedValue{
Number h;
}
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;
@@ -119,3 +79,28 @@ class SerializedError{
}
}
class ExpectedTextValue {
String string;
String regexSource;
String regexFlags;
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;
}
@@ -0,0 +1,121 @@
/*
* 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;
}
}
@@ -18,7 +18,6 @@ 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 java.io.IOException;
@@ -29,21 +28,25 @@ import java.util.LinkedHashMap;
import java.util.Map;
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);
sendMessage("abort", params);
sendMessageAsync("abort", params);
});
}
@Override
public void resume(ResumeOptions options) {
startHandling();
withLogging("Route.resume", () -> resumeImpl(options));
}
@@ -73,11 +76,12 @@ public class RouteImpl extends ChannelOwner implements Route {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
sendMessage("continue", params);
sendMessageAsync("continue", params);
}
@Override
public void fulfill(FulfillOptions options) {
startHandling();
withLogging("Route.fulfill", () -> fulfillImpl(options));
}
@@ -86,16 +90,30 @@ public class RouteImpl extends ChannelOwner implements Route {
options = new FulfillOptions();
}
int status = options.status == null ? 200 : options.status;
String body = "";
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;
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);
}
@@ -107,11 +125,22 @@ 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 (options.headers != null) {
for (Map.Entry<String, String> h : options.headers.entrySet()) {
if (headersOption != null) {
for (Map.Entry<String, String> h : headersOption.entrySet()) {
headers.put(h.getKey().toLowerCase(), h.getValue());
}
}
@@ -128,11 +157,21 @@ public class RouteImpl extends ChannelOwner implements Route {
params.add("headers", Serialization.toProtocol(headers));
params.addProperty("isBase64", isBase64);
params.addProperty("body", body);
sendMessage("fulfill", params);
if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid);
}
sendMessageAsync("fulfill", params);
}
@Override
public Request request() {
public RequestImpl request() {
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
private void startHandling() {
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
handled = true;
}
}
@@ -33,7 +33,7 @@ import java.nio.file.Path;
import java.util.*;
class Serialization {
private static Gson gson = new GsonBuilder()
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
@@ -50,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;
@@ -212,15 +212,19 @@ class Serialization {
static JsonArray toJsonArray(FilePayload[] files) {
JsonArray jsonFiles = new JsonArray();
for (FilePayload p : files) {
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);
jsonFiles.add(toProtocol(p));
}
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) {
@@ -232,11 +236,15 @@ class Serialization {
}
static JsonArray toProtocol(Map<String, String> map) {
return toNameValueArray(map);
}
static JsonArray toNameValueArray(Map<String, ?> map) {
JsonArray array = new JsonArray();
for (Map.Entry<String, String> e : map.entrySet()) {
for (Map.Entry<String, ?> e : map.entrySet()) {
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
item.addProperty("value", e.getValue());
item.add("value", gson().toJsonTree(e.getValue()));
array.add(item);
}
return array;
@@ -0,0 +1,115 @@
/*
* 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 {
private final List<Path> srcDirs;
private final Map<Path, String> classToSourceCache = new HashMap<>();
static StackTraceCollector createFromEnv() {
String srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC");
if (srcRoots == null) {
return null;
}
List<Path> srcDirs = Arrays.stream(srcRoots.split(File.pathSeparator)).map(p -> Paths.get(p)).collect(Collectors.toList());
for (Path srcDir: srcDirs) {
if (!Files.exists(srcDir.toAbsolutePath())) {
throw new PlaywrightException("Source location specified in PLAYWRIGHT_JAVA_SRC doesn't exist: '" + srcDir.toAbsolutePath() + "'");
}
}
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,70 +16,102 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonArray;
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 implements Tracing {
private final BrowserContextImpl context;
class TracingImpl extends ChannelOwner implements Tracing {
LocalUtils localUtils;
boolean isRemote;
private boolean includeSources;
TracingImpl(BrowserContextImpl context) {
this.context = context;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
private void stopChunkImpl(Path path) {
JsonObject params = new JsonObject();
params.addProperty("save", path != null);
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
String mode = "doNotSave";
if (path != null) {
if (isRemote) {
mode = "compressTrace";
} else {
mode = "compressTraceAndSources";
}
}
params.addProperty("mode", mode);
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
if (!json.has("artifact")) {
return;
}
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
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 (context.browser() != null && context.browser().isRemote) {
if (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");
localUtils.zip(path, entries);
}
}
@Override
public void start(StartOptions options) {
context.withLogging("Tracing.start", () -> startImpl(options));
withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk() {
context.withLogging("Tracing.startChunk", () -> {
context.sendMessage("tracingStartChunk");
public void startChunk(StartChunkOptions options) {
withLogging("Tracing.startChunk", () -> {
startChunkImpl(options);
});
}
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();
context.sendMessage("tracingStart", params);
context.sendMessage("tracingStartChunk");
includeSources = options.sources != null;
if (includeSources) {
if (!connection.isCollectingStacks()) {
throw new PlaywrightException("Source root directory must be specified via PLAYWRIGHT_JAVA_SRC environment variable when source collection is enabled");
}
params.addProperty("sources", true);
}
sendMessage("tracingStart", params);
sendMessage("tracingStartChunk");
}
@Override
public void stop(StopOptions options) {
context.withLogging("Tracing.stop", () -> {
withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
context.sendMessage("tracingStop");
sendMessage("tracingStop");
});
}
@Override
public void stopChunk(StopChunkOptions options) {
context.withLogging("Tracing.stopChunk", () -> {
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());
}
private static String resolveUrl(URL baseUrl, String spec) {
static String resolveUrl(URL baseUrl, String spec) {
if (baseUrl == null) {
return spec;
}
@@ -16,7 +16,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.*;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
@@ -24,32 +23,54 @@ import com.microsoft.playwright.options.HttpHeader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
class Utils {
// 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);
}
static <F, T> T convertType(F f, Class<T> t) {
if (f == null) {
return null;
}
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()));
// 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;
}
}
return result;
} catch (Exception e) {
throw new PlaywrightException("Internal error", e);
}
}
@@ -62,7 +83,7 @@ class Utils {
for (int i = 0; i < glob.length(); ++i) {
char c = glob.charAt(i);
if (escapeGlobChars.contains(c)) {
tokens.append("\\" + c);
tokens.append("\\").append(c);
continue;
}
if (c == '*') {
@@ -100,7 +121,7 @@ class Utils {
tokens.append('|');
break;
}
tokens.append("\\" + c);
tokens.append("\\").append(c);
break;
default:
tokens.append(c);
@@ -126,17 +147,21 @@ class Utils {
static FilePayload[] toFilePayloads(Path[] files) {
List<FilePayload> payloads = new ArrayList<>();
for (Path file : files) {
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));
payloads.add(toFilePayload(file));
}
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) {
@@ -144,7 +169,7 @@ class Utils {
try {
Files.createDirectories(dir);
} catch (IOException e) {
throw new PlaywrightException("Failed to create parent directory: " + dir.toString(), e);
throw new PlaywrightException("Failed to create parent directory: " + dir, e);
}
}
}
@@ -177,7 +202,7 @@ class Utils {
}
static boolean isSafeCloseError(String error) {
return error.endsWith("Browser has been closed") || error.endsWith("Target page, context or browser has been closed");
return error.contains("Browser has been closed") || error.contains("Target page, context or browser has been closed");
}
static String createGuid() {
@@ -196,4 +221,24 @@ class Utils {
}
return map;
}
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;
}
}
@@ -22,7 +22,4 @@ 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);
}
}
@@ -0,0 +1,77 @@
/*
* 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);
}
@@ -0,0 +1,133 @@
/*
* 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}.
* <pre>{@code
* context.request().post(
* "https://example.com/submit",
* RequestOptions.create()
* .setQueryParam("page", 1)
* .setData("My data"));
* }</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);
}
@@ -19,5 +19,6 @@ package com.microsoft.playwright.options;
public enum WaitUntilState {
LOAD,
DOMCONTENTLOADED,
NETWORKIDLE
NETWORKIDLE,
COMMIT
}
@@ -23,7 +23,6 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.security.KeyStore;
class HttpsConfiguratorImpl extends HttpsConfigurator {
@@ -52,8 +51,7 @@ class HttpsConfiguratorImpl extends HttpsConfigurator {
String password = "password";
// Generated via
// keytool -genkey -keyalg RSA -validity 36500 -keysize 4096 -dname cn=Playwright,ou=Playwright,o=Playwright,c=US -keystore keystore.jks -storepass password -keypass password
ks.load(new FileInputStream("src/test/resources/keys/keystore.jks"), password.toCharArray());
ks.load(HttpsConfiguratorImpl.class.getClassLoader().getResourceAsStream("resources/keys/keystore.jks"), password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password.toCharArray());
@@ -35,7 +35,6 @@ public class Server implements HttpHandler {
public final String CROSS_PROCESS_PREFIX;
public final int PORT;
public final String EMPTY_PAGE;
private final File resourcesDir;
private final Map<String, CompletableFuture<Request>> requestSubscribers = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Auth> auths = Collections.synchronizedMap(new HashMap<>());
@@ -79,7 +78,6 @@ public class Server implements HttpHandler {
server.setExecutor(null); // creates a default executor
File cwd = FileSystems.getDefault().getPath(".").toFile();
resourcesDir = new File(cwd, "src/test/resources");
server.start();
}
@@ -100,12 +98,14 @@ public class Server implements HttpHandler {
}
static class Request {
public final String url;
public final String method;
// TODO: make a copy to ensure thread safety?
public final Headers headers;
public final byte[] postBody;
Request(HttpExchange exchange) throws IOException {
url = exchange.getRequestURI().toString();
method = exchange.getRequestMethod();
headers = exchange.getRequestHeaders();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -165,8 +165,6 @@ public class Server implements HttpHandler {
exchange.sendResponseHeaders(401, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("HTTP Error 401 Unauthorized: Access is denied");
// TODO: notify subscriber?
exchange.getResponseBody().close();
}
return;
}
@@ -192,21 +190,24 @@ public class Server implements HttpHandler {
if ("/".equals(path)) {
path = "/index.html";
}
File file = new File(resourcesDir, path.substring(1));
if (!file.exists()) {
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
String resourcePath = "resources" + path;
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
if (resource == null) {
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + file.getCanonicalPath());
writer.write("File not found: " + resourcePath);
}
return;
}
exchange.getResponseHeaders().add("Content-Type", mimeType(file));
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath)));
ByteArrayOutputStream body = new ByteArrayOutputStream();
OutputStream output = body;
if (gzipRoutes.contains(path)) {
exchange.getResponseHeaders().add("Content-Encoding", "gzip");
}
try (FileInputStream input = new FileInputStream(file)) {
try (InputStream input = resource) {
if (gzipRoutes.contains(path)) {
output = new GZIPOutputStream(output);
}
@@ -0,0 +1,51 @@
/*
* 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 org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAPIResponseAssertions extends TestBase {
@Test
void passWithResponse() {
APIResponse res = page.request().get(server.EMPTY_PAGE);
assertThat(res).isOK();
}
@Test
void passWithNot() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
assertThat(res).not().isOK();
}
@Test
void fail() {
APIResponse res = page.request().get(server.PREFIX + "/unknown");
boolean didThrow = false;
try {
assertThat(res).isOK();
} catch (AssertionFailedError e) {
didThrow = true;
assertTrue(e.getMessage().contains("→ GET " + server.PREFIX + "/unknown"), "Actual error: " + e.toString());
assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString());
}
assertTrue(didThrow);
}
}
@@ -26,7 +26,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static com.microsoft.playwright.Utils.assertJsonEquals;
import static com.microsoft.playwright.Utils.getOS;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -58,6 +57,7 @@ public class TestBrowserContextAddCookies extends TestBase {
.setDomain(cookies.get(0).domain)
.setPath(cookies.get(0).path)
.setExpires(cookies.get(0).expires)
.setSameSite(cookies.get(0).sameSite)
));
assertJsonEquals(new Gson().toJson(cookies), context.cookies());
}
@@ -346,21 +346,7 @@ public class TestBrowserContextAddCookies extends TestBase {
"}", server.CROSS_PROCESS_PREFIX + "/grid.html");
page.frames().get(1).evaluate("document.cookie = 'username=John Doe'");
page.waitForTimeout(2000);
boolean allowsThirdParty = isFirefox();
List<Cookie> cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html");
if (allowsThirdParty) {
assertJsonEquals("[{\n" +
" 'domain': '127.0.0.1',\n" +
" 'expires': -1,\n" +
" 'httpOnly': false,\n" +
" 'name': 'username',\n" +
" 'path': '/',\n" +
" 'sameSite': 'NONE',\n" +
" 'secure': false,\n" +
" 'value': 'John Doe'\n" +
"}]", cookies);
} else {
assertEquals(0, cookies.size());
}
assertEquals(0, cookies.size());
}
}
@@ -50,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" }]", cookies);
}
@@ -74,7 +74,7 @@ public class TestBrowserContextCookies extends TestBase {
assertEquals(timestamp, cookie.expires);
assertEquals(false, cookie.httpOnly);
assertEquals(false, cookie.secure);
if (isChromium()) {
if (isChromium() || isFirefox()) {
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
} else {
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
@@ -146,7 +146,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" },\n" +
" {\n" +
" name: 'username',\n" +
@@ -156,7 +156,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
" }\n" +
"]", cookies);
}
@@ -32,11 +32,8 @@ public class TestBrowserContextCredentials extends TestBase {
@DisabledIf(value="isChromiumHeadful", disabledReason="fail")
void shouldFailWithoutCredentials() {
server.setAuth("/empty.html", "user", "pass");
BrowserContext context = browser.newContext();
Page page = context.newPage();
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(401, response.status());
context.close();
}
void shouldWorkWithSetHTTPCredentials() {
@@ -46,34 +43,35 @@ public class TestBrowserContextCredentials extends TestBase {
@Test
void shouldWorkWithCorrectCredentials() {
server.setAuth("/empty.html", "user", "pass");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setHttpCredentials("user", "pass"));
Page page = context.newPage();
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
context.close();
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setHttpCredentials("user", "pass"))) {
Page page = context.newPage();
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
}
}
@Test
void shouldFailWithWrongCredentials() {
server.setAuth("/empty.html", "user", "pass");
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setHttpCredentials("foo", "bar"));
Page page = context.newPage();
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(401, response.status());
context.close();
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setHttpCredentials("foo", "bar"))) {
Page page = context.newPage();
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(401, response.status());
}
}
@Test
void shouldReturnResourceBody() {
server.setAuth("/playground.html", "user", "pass");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setHttpCredentials("user", "pass"));
Page page = context.newPage();
Response response = page.navigate(server.PREFIX + "/playground.html");
assertEquals(200, response.status());
assertEquals("Playground", page.title());
assertTrue(new String(response.body()).contains("Playground"));
context.close();
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setHttpCredentials("user", "pass"))) {
Page page = context.newPage();
Response response = page.navigate(server.PREFIX + "/playground.html");
assertEquals(200, response.status());
assertEquals("Playground", page.title());
assertTrue(new String(response.body()).contains("Playground"));
}
}
}
@@ -0,0 +1,683 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.microsoft.playwright.options.*;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static com.microsoft.playwright.Utils.*;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextFetch extends TestBase {
@Test
void getShouldWork() {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertNotNull(response.ok());
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void fetchShouldWork() {
APIResponse response = context.request().fetch(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertNotNull(response.ok());
assertEquals(server.PREFIX + "/simple.json", response.url());
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
// server.setRoute("/one-style.css", exchange -> exchange.getResponseBody().close());
@Test
void shouldThrowOnNetworkError() {
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
try {
context.request().get(server.PREFIX + "/test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
}
@Test
void shouldThrowOnNetworkErrorAfterRedirect() {
server.setRedirect("/redirect", "/test");
server.setRoute("/test", exchange -> exchange.getResponseBody().close());
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("socket hang up"), e.getMessage());
}
}
@Test
void shouldThrowOnNetworkErrorWhenSendingBody() {
server.setRoute("/test", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4096);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<title>A");
}
});
try {
context.request().get(server.PREFIX + "/test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
}
@Test
void shouldThrowOnNetworkErrorWhenSendingBodyAfterRedirect() {
server.setRedirect("/redirect", "/test");
server.setRoute("/test", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4096);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<title>A");
}
});
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("aborted"), e.getMessage());
}
}
@Test
void shouldAddSessionCookiesToRequest() throws ExecutionException, InterruptedException {
Cookie cookie = new Cookie("username", "John Doe");
cookie.domain = "localhost";
cookie.path = "/";
cookie.expires = -1.0;
cookie.httpOnly = false;
cookie.secure = false;
cookie.sameSite = SameSiteAttribute.LAX;
context.addCookies(asList(cookie));
Future<Server.Request> req = server.futureRequest("/simple.json");
context.request().get(server.PREFIX + "/simple.json");
assertEquals(asList("username=John Doe"), req.get().headers.get("cookie"));
}
@Test
void getShouldSupportQueryParams() throws ExecutionException, InterruptedException {
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().get(server.EMPTY_PAGE + "?p1=foo",
RequestOptions.create().setQueryParam("p1", "v1").setQueryParam("парам2", "знач2"));
assertNotNull(req.get());
assertEquals("/empty.html?p1=v1&%D0%BF%D0%B0%D1%80%D0%B0%D0%BC2=%D0%B7%D0%BD%D0%B0%D1%872", req.get().url);
}
;
@Test
void getShouldSupportFailOnStatusCode() {
try {
context.request().get(server.PREFIX + "/does-not-exist.html", RequestOptions.create().setFailOnStatusCode(true));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage());
}
}
@Test
@Disabled("Error: socket hang up")
void getShouldSupportIgnoreHTTPSErrorsOption() {
APIResponse response = context.request().get(httpsServer.EMPTY_PAGE, RequestOptions.create().setIgnoreHTTPSErrors(true));
assertEquals(200, response.status());
}
@Test
void shouldNotAddContextCookieIfCookieHeaderPassedAsAParameter() throws ExecutionException, InterruptedException {
Cookie cookie = new Cookie("username", "John Doe");
cookie.domain = "localhost";
cookie.path = "/";
cookie.expires = -1.0;
cookie.httpOnly = false;
cookie.secure = false;
cookie.sameSite = SameSiteAttribute.LAX;
context.addCookies(asList(cookie));
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().get(server.EMPTY_PAGE, RequestOptions.create().setHeader("Cookie", "foo=bar"));
assertEquals(asList("foo=bar"), req.get().headers.get("cookie"));
}
@Test
void shouldFollowRedirects() throws ExecutionException, InterruptedException {
server.setRedirect("/redirect1", "/redirect2");
server.setRedirect("/redirect2", "/simple.json");
Cookie cookie = new Cookie("username", "John Doe");
cookie.domain = "localhost";
cookie.path = "/";
cookie.expires = -1.0;
cookie.httpOnly = false;
cookie.secure = false;
cookie.sameSite = SameSiteAttribute.LAX;
context.addCookies(asList(cookie));
Future<Server.Request> req = server.futureRequest("/simple.json");
APIResponse response = context.request().get(server.PREFIX + "/redirect1");
assertEquals(asList("username=John Doe"), req.get().headers.get("cookie"));
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldAddCookiesFromSetCookieHeader() {
server.setRoute("/setcookie.html", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "session=value");
exchange.getResponseHeaders().add("Set-Cookie", "foo=bar; max-age=3600");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
context.request().get(server.PREFIX + "/setcookie.html");
List<Cookie> cookies = context.cookies();
assertEquals(2, cookies.size());
cookies.sort(Comparator.comparing(a -> a.name));
assertEquals("foo", cookies.get(0).name);
assertEquals("bar", cookies.get(0).value);
assertEquals("session", cookies.get(1).name);
assertEquals("value", cookies.get(1).value);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList("foo=bar", "session=value"), page.evaluate("() => document.cookie.split(';').map(s => s.trim()).sort()"));
}
@Test
@Disabled("Default Java's HTTP server throws on 'CONNECT non-existent.com:80 HTTP/1.1' because path is null.")
void shouldWorkWithContextLevelProxy() throws ExecutionException, InterruptedException {
server.setRoute("/target.html", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<title>Served by the proxy</title>");
}
});
try (Browser browser = browserType.launch(new BrowserType.LaunchOptions().setProxy("http://per-context"))) {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy("localhost:" + server.PORT));
Future<Server.Request> request = server.futureRequest("/target.html");
APIResponse response = context.request().get("http://non-existent.com/target.html");
assertEquals(200, response.status());
assertEquals("/target.html", request.get().url);
}
}
@Test
void shouldWorkWithHttpCredentials() throws ExecutionException, InterruptedException {
server.setAuth("/empty.html", "user", "pass");
String base64 = Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8));;
Future<Server.Request> request = server.futureRequest("/empty.html");
APIResponse response = context.request().get(server.EMPTY_PAGE, RequestOptions.create()
.setHeader("authorization", "Basic " + base64));
assertEquals(200, response.status());
assertEquals("/empty.html", request.get().url);
}
@Test
void shouldWorkWithSetHTTPCredentials() {
server.setAuth("/empty.html", "user", "pass");
APIResponse response1 = context.request().get(server.EMPTY_PAGE);
assertEquals(401, response1.status());
try (BrowserContext context2 = browser.newContext(
new Browser.NewContextOptions().setHttpCredentials("user", "pass"))) {
APIResponse response2 = context2.request().get(server.EMPTY_PAGE);
assertEquals(200, response2.status());
}
}
@Test
void shouldReturnErrorWithWrongCredentials() {
server.setAuth("/empty.html", "user", "pass");
try (BrowserContext context = browser.newContext(
new Browser.NewContextOptions().setHttpCredentials("user", "wrong"))) {
APIResponse response = context.request().get(server.EMPTY_PAGE);
assertEquals(401, response.status());
}
}
@Test
void postShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().post(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("POST", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void deleteShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().delete(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("DELETE", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void patchShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().patch(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("PATCH", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void putShouldSupportPostData() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/simple.json");
APIResponse response = context.request().put(server.PREFIX + "/simple.json",
RequestOptions.create().setData("My request"));
assertEquals("PUT", request.get().method);
assertEquals("My request", new String(request.get().postBody));
assertEquals(200, response.status());
assertEquals("/simple.json", request.get().url);
}
@Test
void shouldAddDefaultHeaders() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/empty.html");
context.request().get(server.EMPTY_PAGE);
assertEquals(asList("*/*"), request.get().headers.get("accept"));
Object userAgent = page.evaluate("() => navigator.userAgent");
assertEquals(asList(userAgent), request.get().headers.get("user-agent"));
assertEquals(asList("gzip,deflate,br"), request.get().headers.get("accept-encoding"));
}
@Test
void shouldSendContentLength() throws ExecutionException, InterruptedException {
byte[] bytes = new byte[256];
for (int i = 0; i < 256; i++) {
bytes[i] = (byte) i;
}
Future<Server.Request> request = server.futureRequest("/empty.html");
context.request().post(server.EMPTY_PAGE, RequestOptions.create().setData(bytes));
assertEquals(asList("256"), request.get().headers.get("content-length"));
assertEquals(asList("application/octet-stream"), request.get().headers.get("content-type"));
}
@Test
void shouldAddDefaultHeadersToRedirects() throws ExecutionException, InterruptedException {
server.setRedirect("/redirect", "/empty.html");
Future<Server.Request> request = server.futureRequest("/empty.html");
context.request().get(server.PREFIX + "/redirect");
assertEquals(asList("*/*"), request.get().headers.get("accept"));
Object userAgent = page.evaluate("() => navigator.userAgent");
assertEquals(asList(userAgent), request.get().headers.get("user-agent"));
assertEquals(asList("gzip,deflate,br"), request.get().headers.get("accept-encoding"));
}
@Test
void shouldAllowToOverrideDefaultHeaders() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/empty.html");
context.request().get(server.EMPTY_PAGE, RequestOptions.create()
.setHeader(
"User-Agent", "Playwright")
.setHeader("Accept", "text/html")
.setHeader("Accept-Encoding", "br"));
assertEquals(asList("text/html"), request.get().headers.get("accept"));
assertEquals(asList("Playwright"), request.get().headers.get("user-agent"));
assertEquals(asList("br"), request.get().headers.get("accept-encoding"));
}
@Test
void shouldPropagateCustomHeadersWithRedirects() throws ExecutionException, InterruptedException {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
Future<Server.Request> req1 = server.futureRequest("/a/redirect1");
Future<Server.Request> req2 = server.futureRequest("/b/c/redirect2");
Future<Server.Request> req3 = server.futureRequest("/simple.json");
context.request().get(server.PREFIX + "/a/redirect1",
RequestOptions.create().setHeader("foo", "bar"));
assertEquals(asList("bar"), req1.get().headers.get("foo"));
assertEquals(asList("bar"), req2.get().headers.get("foo"));
assertEquals(asList("bar"), req3.get().headers.get("foo"));
}
@Test
void shouldPropagateExtraHttpHeadersWithRedirects() throws ExecutionException, InterruptedException {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
context.setExtraHTTPHeaders(mapOf("My-Secret", "Value"));
Future<Server.Request> req1 = server.futureRequest("/a/redirect1");
Future<Server.Request> req2 = server.futureRequest("/b/c/redirect2");
Future<Server.Request> req3 = server.futureRequest("/simple.json");
context.request().get(server.PREFIX + "/a/redirect1");
assertEquals(asList("Value"), req1.get().headers.get("my-secret"));
assertEquals(asList("Value"), req2.get().headers.get("my-secret"));
assertEquals(asList("Value"), req3.get().headers.get("my-secret"));
}
@Test
void shouldThrowOnInvalidHeaderValue() {
try {
context.request().get(server.EMPTY_PAGE, RequestOptions.create()
.setHeader("foo", "недопустимое значение"));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage());
}
}
@Test
void shouldThrowOnNonHttpSProtocol() {
try {
context.request().get("data:text/plain,test");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Protocol \"data:\" not supported"), e.getMessage());
}
try {
context.request().get("file:///tmp/foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Protocol \"file:\" not supported"), e.getMessage());
}
}
@Test
void shouldSupportTimeoutOption() {
server.setRoute("/slow", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4096);
});
try {
context.request().get(server.PREFIX + "/slow", RequestOptions.create().setTimeout(100));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
}
@Test
void shouldSupportATimeoutOf0() {
server.setRoute("/slow", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("done");
}
});
APIResponse response = context.request().get(server.PREFIX + "/slow",
RequestOptions.create().setTimeout(0));
assertEquals("done", response.text());
}
@Test
void shouldRespectTimeoutAfterRedirects() {
server.setRedirect("/redirect", "/slow");
server.setRoute("/slow", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4096);
});
context.setDefaultTimeout(100);
try {
context.request().get(server.PREFIX + "/redirect");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
}
@Test
void shouldDispose() {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
response.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
}
@Test
void shouldDisposeWhenContextCloses() {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
context.close();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed") ||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
}
@Test
void shouldOverrideRequestParameters() throws ExecutionException, InterruptedException {
Request pageReq = page.waitForRequest("**/*", () -> page.navigate(server.EMPTY_PAGE));
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().fetch(pageReq, RequestOptions.create().setMethod("POST")
.setHeader("foo", "bar")
.setData("data"));
assertEquals("POST", req.get().method);
assertEquals(asList("bar"), req.get().headers.get("foo"));
assertEquals("data", new String(req.get().postBody));
}
@Test
void shouldSupportApplicationXWwwFormUrlencoded() throws ExecutionException, InterruptedException {
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().post(server.EMPTY_PAGE, RequestOptions.create().setForm(
FormData.create()
.set("firstName", "John")
.set("lastName", "Doe")
.set("file", "f.js")));
assertEquals("POST", req.get().method);
assertEquals(asList("application/x-www-form-urlencoded"), req.get().headers.get("content-type"));
String body = new String(req.get().postBody);
assertTrue(body.contains("firstName=John"));
assertTrue(body.contains("lastName=Doe"));
assertTrue(body.contains("file=f.js"));
}
@Test
void shouldEncodeToApplicationJsonByDefault() throws ExecutionException, InterruptedException {
Map<String, Object> data = mapOf(
"firstName", "John",
"lastName", "Doe",
"file", mapOf("name", "f.js")
);
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().post(server.EMPTY_PAGE, RequestOptions.create().setData(data));
assertEquals("POST", req.get().method);
assertEquals(asList("application/json"), req.get().headers.get("content-type"));
String body = new String(req.get().postBody);
assertEquals(new Gson().toJson(data), body);
}
@Test
void shouldSupportMultipartFormData() throws ExecutionException, InterruptedException {
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
FilePayload file = new FilePayload("f.js", "text/javascript",
"var x = 10;\r\n;console.log(x);".getBytes(StandardCharsets.UTF_8));
APIResponse response = context.request().post(server.EMPTY_PAGE, RequestOptions.create().setMultipart(
FormData.create()
.set("firstName", "John")
.set("lastName", "Doe")
.set("file", file)));
assertEquals("POST", serverRequest.get().method);
List<String> contentType = serverRequest.get().headers.get("content-type");
assertNotNull(contentType);
assertEquals(1, contentType.size());
assertTrue(contentType.get(0).contains("multipart/form-data"), contentType.get(0));
String body = new String(serverRequest.get().postBody);
assertTrue(body.contains("content-disposition: form-data; name=\"firstName\"\r\n" +
"\r\n" +
"John"), body);
assertTrue(body.contains("content-disposition: form-data; name=\"lastName\"\r\n" +
"\r\n" +
"Doe"), body);
assertTrue(body.contains("content-disposition: form-data; name=\"file\"; filename=\"f.js\"\r\n" +
"content-type: text/javascript\r\n" +
"\r\n" +
"var x = 10;\r\n" +
";console.log(x);"), body);
assertEquals(200, response.status());
}
@Test
void shouldSupportMultipartFormDataWithPathValues(@TempDir Path tmp) throws ExecutionException, InterruptedException, IOException {
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
Path path = tmp.resolve("simplezip.json");
try (FileOutputStream output = new FileOutputStream(path.toFile())) {
output.write("{\"foo\":\"bar\"}".getBytes(StandardCharsets.UTF_8));
}
APIResponse response = context.request().post(server.EMPTY_PAGE, RequestOptions.create().setMultipart(
FormData.create()
.set("firstName", "John")
.set("lastName", "Doe")
.set("file", path)));
assertEquals("POST", serverRequest.get().method);
List<String> contentType = serverRequest.get().headers.get("content-type");
assertNotNull(contentType);
assertEquals(1, contentType.size());
assertTrue(contentType.get(0).contains("multipart/form-data"), contentType.get(0));
String body = new String(serverRequest.get().postBody);
assertTrue(body.contains("content-disposition: form-data; name=\"firstName\"\r\n" +
"\r\n" +
"John"), body);
assertTrue(body.contains("content-disposition: form-data; name=\"lastName\"\r\n" +
"\r\n" +
"Doe"), body);
assertTrue(body.contains("content-disposition: form-data; name=\"file\"; filename=\"simplezip.json\"\r\n" +
"content-type: application/json\r\n" +
"\r\n" +
"{\"foo\":\"bar\"}"), body);
assertEquals(200, response.status());
}
@Test
void shouldSerializeDataToJsonRegardlessOfContentType() throws ExecutionException, InterruptedException {
Map<String, Object> data = mapOf(
"firstName", "John",
"lastName", "Doe");
Future<Server.Request> req = server.futureRequest("/empty.html");
context.request().post(server.EMPTY_PAGE, RequestOptions.create()
.setHeader("content-type", "unknown")
.setData(data));
assertEquals("POST", req.get().method);
assertEquals(asList("unknown"), req.get().headers.get("content-type"));
String body = new String(req.get().postBody);
assertEquals(new Gson().toJson(data), body);
}
@Test
void shouldThrowWhenDataPassedForUnsupportedRequest() {
try {
context.request().fetch(server.EMPTY_PAGE, RequestOptions.create()
.setMethod("GET").setData("bar"));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Method GET does not accept post data"), e.getMessage());
}
}
@Test
void contextRequestShouldExportSameStorageStateAsContext() {
server.setRoute("/setcookie.html", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "a=b");
exchange.getResponseHeaders().add("Set-Cookie", "c=d");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
context.request().get(server.PREFIX + "/setcookie.html");
String contextState = context.storageState();
assertEquals(2, context.cookies().size());
String requestState = context.request().storageState();
assertEquals(contextState, requestState);
String pageState = page.request().storageState();
assertEquals(contextState, pageState);
}
@Test
void shouldAcceptBoolAndNumericParams() throws ExecutionException, InterruptedException {
Future<Server.Request> req = server.futureRequest("/empty.html");
page.request().get(server.EMPTY_PAGE, RequestOptions.create()
.setQueryParam("str", "s")
.setQueryParam("num", 10)
.setQueryParam("bool", true)
.setQueryParam("bool2", false));
assertEquals("/empty.html?str=s&num=10&bool=true&bool2=false", req.get().url);
}
@Test
void shouldAbortRequestsWhenBrowserContextCloses() {
server.setRoute("/empty.html", exchange -> {
});
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.exposeFunction("closeContext", (Object... args) -> {
context.close();
return null;
});
page.evaluate("() => setTimeout(closeContext, 1000);");
try {
context.request().get(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
}
try {
context.request().post(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
}
}
@@ -16,10 +16,13 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import static java.util.Arrays.asList;
@@ -134,4 +137,70 @@ public class TestBrowserContextRoute extends TestBase {
page.navigate(server.EMPTY_PAGE);
assertEquals(2, intercepted[0]);
}
@Test
void shouldOverwritePostBodyWithEmptyString() throws ExecutionException, InterruptedException {
context.route("**/empty.html", route -> {
route.resume(new Route.ResumeOptions().setPostData(""));
});
Future<Server.Request> req = server.futureRequest("/empty.html");
page.setContent("<script>\n" +
" (async () => {\n" +
" await fetch('" + server.EMPTY_PAGE + "', {\n" +
" method: 'POST',\n" +
" body: 'original',\n" +
" });\n" +
" })()\n" +
" </script>");
byte[] body = req.get().postBody;
assertEquals(0, body.length);
}
@Test
void shouldNotSwallowExceptionsInRoute() throws ExecutionException, InterruptedException {
context.route("**/empty.html", route -> {
throw new RuntimeException("My Exception");
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("My Exception"), e.getMessage());
}
}
@Test
@Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680")
void shouldNotSwallowExceptionsInFulfill() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.EMPTY_PAGE);
response.dispose();
page.route("**/*", route -> {
// Fulfilling with dsiposed response will lead to a server-side exception.
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Fetch response has been disposed"), e.getMessage());
}
}
@Test
@Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680")
void shouldNotSwallowExceptionsInResume() throws ExecutionException, InterruptedException {
page.route("**/*", route -> {
route.resume(new Route.ResumeOptions().setUrl("file:///tmp"));
});
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("New URL must have same protocol as overridden URL"), e.getMessage());
}
}
}
@@ -109,7 +109,7 @@ public class TestBrowserContextStorageState extends TestBase {
" 'expires':-1,\n" +
" 'httpOnly':false,\n" +
" 'secure':false,\n" +
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
" 'sameSite':'" + (isChromium() || isFirefox() ? "Lax" : "None") + "'\n" +
" }],\n" +
" 'origins':[\n" +
" {\n" +
@@ -19,21 +19,22 @@ package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.options.WaitForSelectorState;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.mapOf;
import static com.microsoft.playwright.Utils.parseTrace;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
@@ -59,7 +60,7 @@ public class TestBrowserTypeConnect extends TestBase {
private static BrowserServer launchBrowserServer(BrowserType browserType) {
try {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
@@ -233,7 +234,6 @@ public class TestBrowserTypeConnect extends TestBase {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser has been closed"));
}
}
assertFalse(remote.isConnected());
@@ -258,7 +258,6 @@ public class TestBrowserTypeConnect extends TestBase {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser has been closed"));
}
}
assertFalse(browser.isConnected());
@@ -266,7 +265,7 @@ public class TestBrowserTypeConnect extends TestBase {
page.waitForNavigation(() -> {});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser has been closed"));
assertTrue(e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
}
}
@@ -487,4 +486,36 @@ public class TestBrowserTypeConnect extends TestBase {
assertTrue(Files.exists(traceFile));
assertTrue(Files.size(traceFile) > 0);
}
@Test
void shouldRecordTraceWithSources(@TempDir Path tmpDir) throws IOException {
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source.");
context.tracing().start(new Tracing.StartOptions().setSources(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
Map<String, byte[]> entries = parseTrace(trace);
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertEquals(1, sources.size());
String path = getClass().getName().replace('.', File.separatorChar);
Path sourceFile = Paths.get(System.getenv("PLAYWRIGHT_JAVA_SRC"), path + ".java");
byte[] thisFile = Files.readAllBytes(sourceFile);
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
}
@Test
void shouldFulfillWithGlobalFetchResult() {
page.route("**/*", route -> {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.PREFIX + "/simple.json");
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
}
@@ -111,16 +111,18 @@ public class TestDownload extends TestBase {
@Test
void shouldReportDownloadsWithAcceptDownloadsFalse() {
page.setContent("<a href='" + server.PREFIX + "/downloadWithFilename'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals(server.PREFIX + "/downloadWithFilename", download.url());
assertEquals("file.txt", download.suggestedFilename());
try {
download.path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(download.failure().contains("acceptDownloads"));
assertTrue(e.getMessage().contains("acceptDownloads: true"));
try (Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(false))) {
page.setContent("<a href='" + server.PREFIX + "/downloadWithFilename'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals(server.PREFIX + "/downloadWithFilename", download.url());
assertEquals("file.txt", download.suggestedFilename());
try {
download.path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(download.failure().contains("acceptDownloads"));
assertTrue(e.getMessage().contains("acceptDownloads: true"));
}
}
}
@Test
@@ -45,7 +45,7 @@ public class TestElementHandleBoundingBox extends TestBase {
@Test
void shouldHandleNestedFrames() {
page.setViewportSize(500, 500);
page.setViewportSize(616, 500);
page.navigate(server.PREFIX + "/frames/nested-frames.html");
Frame nestedFrame = page.frame("dos");
assertNotNull(nestedFrame);
@@ -0,0 +1,324 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.RequestOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestGlobalFetch extends TestBase {
@Test
void shouldHaveJavaInDefaultUesrAgent() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions());
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
APIResponse response = request.get(server.EMPTY_PAGE);
assertTrue(response.ok());
assertEquals(server.EMPTY_PAGE, response.url());
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
assertTrue(serverRequest.get().headers.get("user-agent").get(0).contains("java/" + version));
}
@Test
void fetchShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.fetch(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void deleteShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.delete(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void getShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void headShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.head(server.EMPTY_PAGE);
assertEquals(server.EMPTY_PAGE, response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("text/html", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("text/html", contentType.get().value);
assertEquals("", response.text());
}
@Test
void patchShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.patch(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void postShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.post(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void putShouldWork() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.put(server.PREFIX + "/simple.json");
assertEquals(server.PREFIX + "/simple.json", response.url());
assertEquals(200, response.status());
assertEquals("OK", response.statusText());
assertTrue(response.ok());
assertEquals("application/json", response.headers().get("content-type"));
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
assertTrue(contentType.isPresent());
assertEquals("application/json", contentType.get().value);
assertEquals("", response.text());
}
@Test
void shouldDisposeGlobalRequest() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
request.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
}
@Test
void shouldSupportGlobalUserAgentOption() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setUserAgent("My Agent"));
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
APIResponse response = request.get(server.EMPTY_PAGE);
assertTrue(response.ok());
assertEquals(server.EMPTY_PAGE, response.url());
assertEquals(asList("My Agent"), serverRequest.get().headers.get("user-agent"));
}
@Test
void shouldSupportGlobalTimeoutOption() {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setTimeout(1));
server.setRoute("/empty.html", exchange -> {});
try {
request.get(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Request timed out after 1ms"), e.getMessage());
}
}
@Test
void shouldPropagateExtraHttpHeadersWithRedirects() throws ExecutionException, InterruptedException {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setExtraHTTPHeaders(mapOf("My-Secret", "Value")));
Future<Server.Request> req1 = server.futureRequest("/a/redirect1");
Future<Server.Request> req2 = server.futureRequest("/b/c/redirect2");
Future<Server.Request> req3 = server.futureRequest("/simple.json");
request.get(server.PREFIX + "/a/redirect1");
assertEquals(asList("Value"), req1.get().headers.get("my-secret"));
assertEquals(asList("Value"), req2.get().headers.get("my-secret"));
assertEquals(asList("Value"), req3.get().headers.get("my-secret"));
}
@Test
void shouldSupportGlobalHttpCredentialsOption() {
server.setAuth("/empty.html", "user", "pass");
APIRequestContext request1 = playwright.request().newContext();
APIResponse response1 = request1.get(server.EMPTY_PAGE);
assertEquals(401, response1.status());
request1.dispose();
APIRequestContext request2 = playwright.request().newContext(new APIRequest.NewContextOptions().setHttpCredentials("user", "pass"));
APIResponse response2 = request2.get(server.EMPTY_PAGE);
assertEquals(200, response2.status());
request2.dispose();
}
@Test
void shouldReturnErrorWithWrongCredentials() {
server.setAuth("/empty.html", "user", "pass");
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setHttpCredentials("user", "wrong"));
APIResponse response = request.get(server.EMPTY_PAGE);
assertEquals(401, response.status());
}
void shouldUseSocksProxy() {
}
void shouldPassProxyCredentials() {
}
@Test
@Disabled("Error: socket hang up")
void shouldSupportGlobalIgnoreHTTPSErrorsOption() {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setIgnoreHTTPSErrors(true));
APIResponse response = request.get(httpsServer.EMPTY_PAGE);
assertEquals(200, response.status());
}
@Test
@Disabled("Error: socket hang up")
void shouldPropagateIgnoreHTTPSErrorsOnRedirects() {
httpsServer.setRedirect("/redir", "/empty.html");
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(httpsServer.PREFIX + "/redir", RequestOptions.create().setIgnoreHTTPSErrors(true));
assertEquals(200, response.status());
}
@Test
void shouldResolveUrlRelativeToGobalBaseURLOption() {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setBaseURL(server.PREFIX));
APIResponse response = request.get("/empty.html");
assertEquals(server.EMPTY_PAGE, response.url());
}
@Test
void shouldSetPlaywrightAsUserAgent() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
request.get(server.EMPTY_PAGE);
List<String> headers = serverRequest.get().headers.get("user-agent");
assertNotNull(headers);
assertEquals(1, headers.size());
assertTrue(headers.get(0).startsWith("Playwright/"), headers.get(0));
}
void shouldBeAbleToConstructWithContextOptions() {
}
@Test
void shouldReturnEmptyBody() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.EMPTY_PAGE);
byte[] body = response.body();
assertEquals(0, body.length);
assertEquals("", response.text());
request.dispose();
try {
response.body();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage());
}
}
@Test
void shouldRemoveContentLengthFromReidrectedPostRequests() throws ExecutionException, InterruptedException {
server.setRedirect("/redirect", "/empty.html");
APIRequestContext request = playwright.request().newContext();
Future<Server.Request> req1 = server.futureRequest("/redirect");
Future<Server.Request> req2 = server.futureRequest("/empty.html");
APIResponse result = request.post(server.PREFIX + "/redirect", RequestOptions.create().setData(mapOf("foo", "bar")));
assertEquals(200, result.status());
assertEquals(asList("13"), req1.get().headers.get("content-length"));
assertNull(req2.get().headers.get("content-length"));
request.dispose();
}
private static final List<Object> values = asList(
mapOf("foo", "bar"),
new Object[] {"foo", "bar", 2021},
"foo",
true,
2021
);
@Test
void shouldJsonStringifyTypeBodyWhenContentTypeIsApplicationJson() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
for (Object value : values) {
Future<Server.Request> req = server.futureRequest("/empty.html");
request.post(server.EMPTY_PAGE, RequestOptions.create().setHeader("content-type", "application/json").setData(value));
byte[] body = req.get().postBody;
assertEquals(new Gson().toJson(value), new String(body));
}
request.dispose();
}
@Test
void shouldNotDoubleStringifyTypeBodyWhenContentTypeIsApplicationJson() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
for (Object value : values) {
String stringifiedValue = new Gson().toJson(value);
Future<Server.Request> req = server.futureRequest("/empty.html");
request.post(server.EMPTY_PAGE, RequestOptions.create()
.setHeader("content-type", "application/json")
.setData(stringifiedValue));
byte[] body = req.get().postBody;
assertEquals(stringifiedValue, new String(body));
}
request.dispose();
}
}
@@ -0,0 +1,834 @@
/*
* 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.assertions.LocatorAssertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.mapOf;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestLocatorAssertions extends TestBase {
@Test
void containsTextWRegexPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).containsText(Pattern.compile("ex"));
// Should not normalize whitespace.
assertThat(locator).containsText(Pattern.compile("ext cont"));
}
@Test
void containsTextWRegexCaseInsensitivePass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).containsText(Pattern.compile("text", Pattern.CASE_INSENSITIVE));
}
@Test
void containsTextWRegexMultilinePass() {
page.setContent("<div id=node>Text \nContent</div>");
Locator locator = page.locator("#node");
assertThat(locator).containsText(Pattern.compile("^Content", Pattern.MULTILINE));
}
@Test
void containsTextWRegexDotAllPass() {
page.setContent("<div id=node>foo\nbar</div>");
Locator locator = page.locator("#node");
assertThat(locator).containsText(Pattern.compile("foo.bar", Pattern.DOTALL));
}
@Test
void containsTextWRegexFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).containsText(Pattern.compile("ex2"), new LocatorAssertions.ContainsTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("ex2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to contain regex"), e.getMessage());
}
}
@Test
void hasTextWRegexPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasText(Pattern.compile("Te.t"));
// Should not normalize whitespace.
assertThat(locator).hasText(Pattern.compile("Text.+content"));
}
@Test
void hasTextWRegexFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasText(Pattern.compile("Text 2"), new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text 2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text matching regex"), e.getMessage());
}
}
@Test
void hasTextWTextPass() {
page.setContent("<div id=node><span></span>Text \ncontent&nbsp; </div>");
Locator locator = page.locator("#node");
// Should normalize whitespace.
assertThat(locator).hasText("Text content");
}
@Test
void hasTextWTextFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
// Should normalize whitespace.
try {
assertThat(locator).hasText("Text", new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getValue());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
}
@Test
void hasTextWTextArrayPass() {
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
Locator locator = page.locator("div");
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {"Text 1", "Text 2a"});
}
@Test
void hasTextWTextArrayPassEmpty() {
page.setContent("<div></div>");
Locator locator = page.locator("p");
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {});
}
@Test
void hasTextWTextArrayPassNotEmpty() {
page.setContent("<div><p>Test</p></div>");
Locator locator = page.locator("div");
// Should normalize whitespace.
assertThat(locator).not().hasText(new String[] {});
}
@Test
void hasTextWTextArrayPassOnEmpty() {
page.setContent("<div></div>");
Locator locator = page.locator("p");
// Should normalize whitespace.
assertThat(locator).not().hasText(new String[] {"Test"});
}
@Test
void hasTextWTextArrayFailOnNotEmpty() {
page.setContent("<div></div>");
Locator locator = page.locator("p");
// Should normalize whitespace.
try {
assertThat(locator).not().hasText(new String[] {}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[]", e.getExpected().getStringRepresentation());
assertEquals("null", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage());
}
}
@Test
void hasTextWTextArrayPassLazyPass() {
page.setContent("<div id=div></div>");
Locator locator = page.locator("p");
page.evaluate("setTimeout(() => {\n" +
" div.innerHTML = \"<p>Text 1</p><p>Text 2</p>\";\n" +
"}, 100);");
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {"Text 1", "Text 2"}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
}
@Test
void hasTextWTextArrayFail() {
page.setContent("<div>Text 1</div><div>Text 3</div>");
Locator locator = page.locator("div");
page.evaluate("setTimeout(() => {\n" +
" div.innerHTML = \"<p>Text 1</p><p>Text 2</p>\";\n" +
"}, 100);");
try {
// Should normalize whitespace.
assertThat(locator).hasText(new String[] {"Text 1", "Text 3", "Extra"}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage());
}
}
@Test
void hasTextWRegExArrayPass() {
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
Locator locator = page.locator("div");
// Should normalize whitespace.
assertThat(locator).hasText(new Pattern[] {Pattern.compile( "Text \n1"), Pattern.compile("Text \\d+a")});
}
@Test
void hasTextWRegExArrayFail() {
page.setContent("<div>Text 1</div><div>Text 3</div>");
Locator locator = page.locator("div");
try {
// Should normalize whitespace.
assertThat(locator).hasText(new Pattern[] {Pattern.compile( "Text 1"), Pattern.compile("Text \\d"), Pattern.compile("Extra")}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[Text 1, Text \\d, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
}
@Test
void hasAttributeTextPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasAttribute("id", "node");
}
@Test
void hasAttributeTextFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasAttribute("id", "foo", new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage());
}
}
@Test
void hasAttributeRegExpPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasAttribute("id", Pattern.compile("n..e"));
}
@Test
void hasAttributeRegExpFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasAttribute("id", Pattern.compile(".Nod.."), new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage());
}
}
@Test
void hasClassTextPass() {
page.setContent("<div class=\"foo bar baz\"></div>");
Locator locator = page.locator("div");
assertThat(locator).hasClass("foo bar baz");
}
@Test
void hasClassTextFail() {
page.setContent("<div class=\"bar baz\"></div>");
Locator locator = page.locator("div");
try {
assertThat(locator).hasClass("foo bar baz", new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo bar baz", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
}
@Test
void hasClassRegExpPass() {
page.setContent("<div class=\"foo bar baz\"></div>");
Locator locator = page.locator("div");
assertThat(locator).hasClass(Pattern.compile("foo.* baz"));
}
@Test
void hasClassRegExpFail() {
page.setContent("<div class=\"bar baz\"></div>");
Locator locator = page.locator("div");
try {
assertThat(locator).hasClass(Pattern.compile("foo Z.*"), new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo Z.*", e.getExpected().getStringRepresentation());
assertEquals("bar baz", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
}
@Test
void hasClassTextArrayPass() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
assertThat(locator).hasClass(new String[] {"foo", "bar", "baz"});
}
@Test
void hasClassTextArrayFail() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
try {
assertThat(locator).hasClass(new String[] {"foo", "bar", "missing"}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[foo, bar, missing]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
}
}
@Test
void hasClassRegExpArrayPass() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
assertThat(locator).hasClass(new Pattern[] {Pattern.compile("fo.*"), Pattern.compile(".ar"), Pattern.compile("baz")});
}
@Test
void hasClassRegExpArrayFail() {
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
Locator locator = page.locator("div");
try {
assertThat(locator).hasClass(new Pattern[] {Pattern.compile("fo.*"), Pattern.compile(".ar"), Pattern.compile("baz"), Pattern.compile("extra")}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("[fo.*, .ar, baz, extra]", e.getExpected().getStringRepresentation());
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
}
}
@Test
void hasCountPass() {
page.setContent("<select><option>One</option><option>Two</option></select>");
Locator locator = page.locator("option");
assertThat(locator).hasCount(2);
}
@Test
void hasCountFail() {
page.setContent("<select><option>One</option><option>Two</option></select>");
Locator locator = page.locator("option");
try {
assertThat(locator).hasCount(1, new LocatorAssertions.HasCountOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have count"), e.getMessage());
}
}
@Test
void hasCountPassZero() {
page.setContent("<div></div>");
Locator locator = page.locator("span");
assertThat(locator).hasCount(0);
assertThat(locator).not().hasCount(1);
}
@Test
void hasCSSPass() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasCSS("color", "rgb(255, 0, 0)");
}
@Test
void hasCSSFail() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasCSS("color", "red", new LocatorAssertions.HasCSSOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color'"), e.getMessage());
}
}
@Test
void hasCSSRegExPass() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasCSS("color", Pattern.compile("rgb.*"));
}
@Test
void hasCSSRegExFail() {
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasCSS("color", Pattern.compile("red"), new LocatorAssertions.HasCSSOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("red", e.getExpected().getStringRepresentation());
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color' matching regex"), e.getMessage());
}
}
@Test
void hasIdPass() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasId("node");
}
@Test
void hasIdFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasId("foo", new LocatorAssertions.HasIdOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have ID"), e.getMessage());
}
}
@Test
void hasJSPropertyPass() {
page.setContent("<div></div>");
page.evalOnSelector("div", "e => e.foo = { a: 1, b: 'string' }");
Locator locator = page.locator("div");
assertThat(locator).hasJSProperty("foo", mapOf("a", 1, "b", "string"));
}
@Test
void hasJSPropertyNumberFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
page.evalOnSelector("div", "e => e.foo = 2021");
try {
assertThat(locator).hasJSProperty("foo", 1, new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("1", e.getExpected().getStringRepresentation());
assertEquals("2021", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
}
@Test
void hasJSPropertyObjectFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
page.evalOnSelector("div", "e => e.foo = { a: 1, b: 'string' }");
try {
assertThat(locator).hasJSProperty("foo", mapOf("a", 2), new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("{a=2}", e.getExpected().getStringRepresentation());
assertEquals("{a=1, b=string}", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
}
}
@Test
void hasJSPropertyStringFail() {
page.setContent("<div id=node>Text content</div>");
Locator locator = page.locator("#node");
try {
assertThat(locator).hasJSProperty("id", "foo", new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'id'"), e.getMessage());
}
}
@Test
void hasValueTextPass() {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
assertThat(locator).hasValue("Text content");
}
@Test
void hasValueTextFail() {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
try {
assertThat(locator).hasValue("Text2", new LocatorAssertions.HasValueOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value"), e.getMessage());
}
}
@Test
void hasValueRegExpPass() {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
assertThat(locator).hasValue(Pattern.compile("Text"));
}
@Test
void hasValueRegExpPassWithNot() {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
assertThat(locator).not().hasValue(Pattern.compile("Text2"));
}
@Test
void hasValueRegExpFail() {
page.setContent("<input id=node></input>");
Locator locator = page.locator("#node");
locator.fill("Text content");
try {
assertThat(locator).hasValue(Pattern.compile("Text2"), new LocatorAssertions.HasValueOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("Text2", e.getExpected().getStringRepresentation());
assertEquals("Text content", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have value matching regex"), e.getMessage());
}
}
@Test
void isCheckedPass() {
page.setContent("<input type=checkbox checked></input>");
Locator locator = page.locator("input");
assertThat(locator).isChecked();
}
@Test
void isCheckedFail() {
page.setContent("<input type=checkbox></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be checked"), e.getMessage());
}
}
@Test
void notIsCheckedFail() {
page.setContent("<input type=checkbox checked></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).not().isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be checked"), e.getMessage());
}
}
@Test
void isDisabledPass() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
assertThat(locator).isDisabled();
}
@Test
void isDisabledFail() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
try {
assertThat(locator).isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be disabled"), e.getMessage());
}
}
@Test
void notIsDisabledFail() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
try {
assertThat(locator).not().isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be disabled"), e.getMessage());
}
}
@Test
void isEditablePass() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
assertThat(locator).isEditable();
}
@Test
void isEditableFail() {
page.setContent("<input disabled></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be editable"), e.getMessage());
}
}
@Test
void notIsEditableFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).not().isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be editable"), e.getMessage());
}
}
@Test
void isEmptyPass() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
assertThat(locator).isEmpty();
}
@Test
void isEmptyFail() {
page.setContent("<input value=text></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be empty"), e.getMessage());
}
}
@Test
void notIsEmptyFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).not().isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be empty"), e.getMessage());
}
}
@Test
void isEnabledPass() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
assertThat(locator).isEnabled();
}
@Test
void isEnabledFail() {
page.setContent("<button disabled>Text</button>");
Locator locator = page.locator("button");
try {
assertThat(locator).isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be enabled"), e.getMessage());
}
}
@Test
void notIsEnabledFail() {
page.setContent("<button>Text</button>");
Locator locator = page.locator("button");
try {
assertThat(locator).not().isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be enabled"), e.getMessage());
}
}
@Test
void isFocusedPass() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
locator.focus();
assertThat(locator).isFocused();
}
@Test
void isFocusedFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be focused"), e.getMessage());
}
}
@Test
void notIsFocusedFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
locator.focus();
try {
assertThat(locator).not().isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be focused"), e.getMessage());
}
}
@Test
void isHiddenPass() {
page.setContent("<button style='display: none'></button>");
Locator locator = page.locator("button");
assertThat(locator).isHidden();
}
@Test
void isHiddenFail() {
page.setContent("<button></button>");
Locator locator = page.locator("button");
try {
assertThat(locator).isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be hidden"), e.getMessage());
}
}
@Test
void notIsHiddenFail() {
page.setContent("<button style='display: none'></button>");
Locator locator = page.locator("button");
try {
assertThat(locator).not().isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be hidden"), e.getMessage());
}
}
@Test
void isVisiblePass() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
assertThat(locator).isVisible();
}
@Test
void isVisibleFail() {
page.setContent("<input style='display: none'></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected to be visible"), e.getMessage());
}
}
@Test
void notIsVisibleFail() {
page.setContent("<input></input>");
Locator locator = page.locator("input");
try {
assertThat(locator).not().isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertNull(e.getExpected());
assertNull(e.getActual());
assertTrue(e.getMessage().contains("Locator expected not to be visible"), e.getMessage());
}
}
@Test
void locatorCountShouldWorkWithDeletedMapInMainWorld() {
page.evaluate("Map = 1");
page.locator("#searchResultTableDiv .x-grid3-row").count();
assertThat(page.locator("#searchResultTableDiv .x-grid3-row")).hasCount(0);
}
}
@@ -0,0 +1,254 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.net.MalformedURLException;
import java.net.URL;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestLocatorFrame extends TestBase {
private static void routeIframe(Page page) {
page.route("**/empty.html", route -> route.fulfill(new Route.FulfillOptions()
.setBody("<iframe src='iframe.html'></iframe>").setContentType("text/html")));
page.route("**/iframe.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html>\n" +
" <div>\n" +
" <button>Hello iframe</button>\n" +
" <iframe src='iframe-2.html'></iframe>\n" +
" </div>\n" +
" <span>1</span>\n" +
" <span>2</span>\n" +
" </html>").setContentType("text/html"));
});
page.route("**/iframe-2.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello nested iframe</button></html>").setContentType("text/html"));
});
}
private static void routeAmbiguous(Page page) {
page.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillOptions()
.setBody("<iframe src='iframe-1.html'></iframe>\n" +
"<iframe src='iframe-2.html'></iframe>\n" +
"<iframe src='iframe-3.html'></iframe>")
.setContentType("text/html"));
});
page.route("**/iframe-*", route -> {
try {
String path = new URL(route.request().url()).getPath().substring(1);
route.fulfill(new Route.FulfillOptions()
.setBody("<html><button>Hello from " + path + "</button></html>")
.setContentType("text/html"));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
});
}
@Test
void shouldWorkForIframe() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.frameLocator("iframe").locator("button");
button.waitFor();
assertEquals("Hello iframe", button.innerText());
assertThat(button).hasText("Hello iframe");
button.click();
}
@Test
void shouldWorkForNestedIframe() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.frameLocator("iframe").frameLocator("iframe").locator("button");
button.waitFor();
assertEquals("Hello nested iframe", button.innerText());
assertThat(button).hasText("Hello nested iframe");
button.click();
}
@Test
void shouldWorkForAnd() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator locator = page.frameLocator("iframe").locator("button");
assertThat(locator).hasText("Hello iframe");
assertEquals("Hello iframe", locator.innerText());
Locator spans = page.frameLocator("iframe").locator("span");
assertThat(spans).hasCount(2);
}
@Test
void shouldWaitForFrame() {
page.navigate(server.EMPTY_PAGE);
try {
page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage());
}
}
@Test
void shouldWaitForFrame2() {
routeIframe(page);
page.evaluate("url => setTimeout(() => location.href = url, 300)", server.EMPTY_PAGE);
page.frameLocator("iframe").locator("button").click();
}
void shouldWaitForFrameToGo() {
}
@Test
void shouldNotWaitForFrame() {
page.navigate(server.EMPTY_PAGE);
assertThat(page.frameLocator("iframe").locator("span")).isHidden();
}
@Test
void shouldNotWaitForFrame2() {
page.navigate(server.EMPTY_PAGE);
assertThat(page.frameLocator("iframe").locator("span")).not().isVisible();
}
@Test
void shouldNotWaitForFrame3() {
page.navigate(server.EMPTY_PAGE);
assertThat(page.frameLocator("iframe").locator("span")).hasCount(0);
}
@Test
void shouldClickInLazyIframe() {
page.route("**/iframe.html", route -> {
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello iframe</button></html>").setContentType("text/html"));
});
// empty pge
page.navigate(server.EMPTY_PAGE);
// add blank iframe
page.evaluate("setTimeout(() => {\n" +
" const iframe = document.createElement('iframe');\n" +
" document.body.appendChild(iframe);\n" +
" // navigate iframe\n" +
" setTimeout(() => iframe.src = 'iframe.html', 500);\n" +
" }, 500);");
// Click in iframe
Locator button = page.frameLocator("iframe").locator("button");
button.click();
assertThat(button).hasText("Hello iframe");
assertEquals("Hello iframe", button.innerText());
}
@Test
void waitForShouldSurviveFrameReattach() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
page.evaluate("setTimeout(() => {\n" +
" document.querySelector('iframe').remove();\n" +
" setTimeout(() => {\n" +
" const iframe = document.createElement('iframe');\n" +
" iframe.src = 'iframe-2.html';\n" +
" document.body.appendChild(iframe);\n" +
" }, 500);\n" +
" }, 500);");
button.waitFor();
}
@Test
void clickShouldSurviveFrameReattach() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
page.evaluate("setTimeout(() => {\n" +
" document.querySelector('iframe').remove();\n" +
" setTimeout(() => {\n" +
" const iframe = document.createElement('iframe');\n" +
" iframe.src = 'iframe-2.html';\n" +
" document.body.appendChild(iframe);\n" +
" }, 500);\n" +
" }, 500);");
button.click();
}
@Test
void clickShouldSurviveIframeNavigation() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
page.evaluate("setTimeout(() => {\n" +
" document.querySelector('iframe').src = 'iframe-2.html';\n" +
" }, 500);");
button.click();
}
@Test
void shouldNonWorkForNonFrame() {
routeIframe(page);
page.setContent("<div></div>");
Locator button = page.frameLocator("div").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
}
}
@Test
void locatorFrameLocatorShouldWorkForIframe() {
routeIframe(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
button.waitFor();
assertThat(button).hasText("Hello iframe");
assertEquals("Hello iframe", button.innerText());
button.click();
}
@Test
void locatorFrameLocatorShouldThrowOnAmbiguity() {
routeAmbiguous(page);
page.navigate(server.EMPTY_PAGE);
Locator button = page.locator("body").frameLocator("iframe").locator("button");
try {
button.waitFor();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
}
}
@Test
void locatorFrameLocatorShouldNotThrowOnFirstLastNth() {
routeAmbiguous(page);
page.navigate(server.EMPTY_PAGE);
Locator button1 = page.locator("body").frameLocator("iframe").first().locator("button");
assertThat(button1).hasText("Hello from iframe-1.html");
Locator button2 = page.locator("body").frameLocator("iframe").nth(1).locator("button");
assertThat(button2).hasText("Hello from iframe-2.html");
Locator button3 = page.locator("body").frameLocator("iframe").last().locator("button");
assertThat(button3).hasText("Hello from iframe-3.html");
}
}
@@ -0,0 +1,143 @@
/*
* 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.assertions.PageAssertions;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.util.regex.Pattern;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageAssertions extends TestBase {
@Test
void hasURLTextPass() {
page.navigate("data:text/html,<div>A</div>");
assertThat(page).hasURL("data:text/html,<div>A</div>");
}
@Test
void hasURLTextFail() {
page.navigate("data:text/html,<div>B</div>");
try {
assertThat(page).hasURL("foo", new PageAssertions.HasURLOptions().setTimeout(1_000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getValue());
assertEquals("data:text/html,<div>B</div>", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page URL expected to be"), e.getMessage());
}
}
@Test
void shouldSupportHasUrlWithBaseUrl() {
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) {
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertThat(page).hasURL("/empty.html", new PageAssertions.HasURLOptions().setTimeout(1_000));
}
}
@Test
void notHasUrlText() {
page.navigate("data:text/html,<div>B</div>");
assertThat(page).not().hasURL("about:blank", new PageAssertions.HasURLOptions().setTimeout(1000));
}
@Test
void hasURLRegexPass() {
page.navigate("data:text/html,<div>A</div>");
assertThat(page).hasURL(Pattern.compile("text"));
}
@Test
void hasURLRegexFail() {
page.navigate(server.EMPTY_PAGE);
try {
assertThat(page).hasURL(Pattern.compile(".*foo.*"), new PageAssertions.HasURLOptions().setTimeout(1_000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals(".*foo.*", e.getExpected().getStringRepresentation());
assertEquals(server.EMPTY_PAGE, e.getActual().getValue());
assertTrue(e.getMessage().contains("Page URL expected to match regex"), e.getMessage());
}
}
@Test
void notHasUrlRegEx() {
page.navigate("data:text/html,<div>B</div>");
assertThat(page).not().hasURL(Pattern.compile("about"), new PageAssertions.HasURLOptions().setTimeout(1000));
}
@Test
void hasTitleTextPass() {
page.navigate(server.PREFIX + "/title.html");
assertThat(page).hasTitle("Woof-Woof", new PageAssertions.HasTitleOptions().setTimeout(1_000));
}
@Test
void hasTitleTextNormalizeWhitespaces() {
page.setContent("<title> Foo Bar </title>");
assertThat(page).hasTitle(" Foo Bar", new PageAssertions.HasTitleOptions().setTimeout(1_000));
}
@Test
void hasTitleTextFail() {
page.navigate(server.PREFIX + "/title.html");
try {
assertThat(page).hasTitle("foo", new PageAssertions.HasTitleOptions().setTimeout(1_000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("foo", e.getExpected().getValue());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to be: foo\nReceived: Woof-Woof"), e.getMessage());
}
}
@Test
void hasTitleRegexPass() {
page.navigate(server.PREFIX + "/title.html");
assertThat(page).hasTitle(Pattern.compile("^.oof.+oof$"));
}
@Test
void hasTitleRegexFail() {
page.navigate(server.PREFIX + "/title.html");
try {
assertThat(page).hasTitle(Pattern.compile("^foo[AB]"), new PageAssertions.HasTitleOptions().setTimeout(1_000));
fail("did not throw");
} catch (AssertionFailedError e) {
assertEquals("^foo[AB]", e.getExpected().getStringRepresentation());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to match regex: ^foo[AB]\nReceived: Woof-Woof"), e.getMessage());
}
}
@Test
void notHasTitleRegEx() {
page.navigate(server.PREFIX + "/title.html");
assertThat(page).not().hasTitle(Pattern.compile("ab.ut"));
}
@Test
void hasTitleRegExCaseInsensitivePass() {
page.navigate(server.PREFIX + "/title.html");
assertThat(page).hasTitle(Pattern.compile("woof-woof", Pattern.CASE_INSENSITIVE));
}
}
@@ -87,6 +87,7 @@ public class TestPageDrag extends TestBase {
assertEquals(true, page.evalOnSelector("#target",
"target => target.contains(document.querySelector('#source'))")); // could not find source in target
}
@Test
void shouldAllowSpecifyingThePosition() {
page.setContent("<div style='width:100px;height:100px;background:red;' id='red'>\n" +
@@ -116,4 +117,11 @@ public class TestPageDrag extends TestBase {
Object json = eventsHandle.jsonValue();
assertJsonEquals("[{type: \"mousedown\", x: 34, y: 7},{type: \"mouseup\", x: 10, y: 20}]", json);
}
@Test
void shouldWorkWithLocators() {
page.navigate(server.PREFIX + "/drag-n-drop.html");
page.locator("#source").dragTo(page.locator("#target"));
assertEquals(true, page.evalOnSelector("#target", "target => target.contains(document.querySelector('#source'))"));
}
}
@@ -41,17 +41,48 @@ public class TestPageFill extends TestBase {
@Test
void shouldThrowOnUnsupportedInputs() {
page.navigate(server.PREFIX + "/input/textarea.html");
for (String type : new String[]{"button", "checkbox", "file", "image", "radio", "range", "reset", "submit"}) {
for (String type : new String[]{"button", "checkbox", "file", "image", "radio", "reset", "submit"}) {
page.evalOnSelector("input", "(input, type) => input.setAttribute('type', type)", type);
try {
page.fill("input", "");
fail("fill should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("input of type \"" + type + "\" cannot be filled"), e.getMessage());
assertTrue(e.getMessage().contains("input of type \"" + type + "\" cannot be filled"), "type = " + type + e.getMessage());
}
}
}
@Test
void shouldFillRangeInput() {
page.setContent("<input type=range min=0 max=100 value=50>");
page.fill("input", "42");
assertEquals("42", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldThrowOnIncorrectRangeValue() {
page.setContent("<input type=range min=0 max=100 value=50>");
try {
page.fill("input", "foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed value"), e.getMessage());
}
try {
page.fill("input", "200");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed value"), e.getMessage());
}
try {
page.fill("input", "15.43");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed value"), e.getMessage());
}
}
@Test
void shouldFillDifferentInputTypes() {
page.navigate(server.PREFIX + "/input/textarea.html");
@@ -0,0 +1,34 @@
/*
* 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 org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPageLocatorConvenience extends TestBase {
@Test
void shouldReturnPage() {
page.navigate(server.PREFIX + "/frames/two-frames.html");
Locator outer = page.locator("#outer");
assertEquals(page, outer.page());
Locator inner = outer.locator("#inner");
assertEquals(page, inner.page());
Locator inFrame = page.frames().get(1).locator("div");
assertEquals(page, inFrame.page());
}
}
@@ -0,0 +1,137 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageLocatorQuery extends TestBase {
@Test
void shouldRespectFirstAndLast() {
page.setContent("<section>\n" +
" <div><p>A</p></div>\n" +
" <div><p>A</p><p>A</p></div>\n" +
" <div><p>A</p><p>A</p><p>A</p></div>\n" +
" </section>");
assertEquals(6, page.locator("div >> p").count());
assertEquals(6, page.locator("div").locator("p").count());
assertEquals(1, page.locator("div").first().locator("p").count());
assertEquals(3, page.locator("div").last().locator("p").count());
}
@Test
void shouldRespectNth() {
page.setContent("<section>\n" +
" <div><p>A</p></div>\n" +
" <div><p>A</p><p>A</p></div>\n" +
" <div><p>A</p><p>A</p><p>A</p></div>\n" +
" </section>");
assertEquals(1, page.locator("div >> p").nth(0).count());
assertEquals(2, page.locator("div").nth(1).locator("p").count());
assertEquals(3, page.locator("div").nth(2).locator("p").count());
}
@Test
void shouldThrowOnCaptureWNth() {
page.setContent("<section><div><p>A</p></div></section>");
try {
page.locator("*css=div >> p").nth(1).click();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Can't query n-th element"), e.getMessage());
}
}
@Test
void shouldThrowOnDueToStrictness() {
page.setContent("<div>A</div><div>B</div>");
try {
page.locator("div").isVisible();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("strict mode violation"), e.getMessage());
}
}
@Test
void shouldThrowOnDueToStrictness2() {
page.setContent("<select><option>One</option><option>Two</option></select>");
try {
page.locator("option").evaluate("e => {}");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("strict mode violation"), e.getMessage());
}
}
@Test
void shouldFilterByText() {
page.setContent("<div>Foobar</div><div>Bar</div>");
assertEquals("Foobar", page.locator("div", new Page.LocatorOptions().setHasText("Foo")).textContent());
}
@Test
void shouldFilterByText2() {
page.setContent("<div>foo <span>hello world</span> bar</div>");
assertEquals("foo hello world bar", page.locator("div", new Page.LocatorOptions().setHasText("hello world")).textContent());
}
@Test
void shouldFilterByRegex() {
page.setContent("<div>Foobar</div><div>Bar</div>");
assertEquals("Foobar", page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("Foo.*"))).textContent());
}
@Test
void shouldFilterByTextWithQuotes() {
page.setContent("<div>Hello \"world\"</div><div>Hello world</div>");
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText("Hello \"world\"")).textContent());
}
@Test
void shouldFilterByRegexWithQuotes() {
page.setContent("<div>Hello \"world\"</div><div>Hello world</div>");
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("Hello \"world\""))).textContent());
}
@Test
void shouldFilterByRegexAndRegexpFlags() {
page.setContent("<div>Hello \"world\"</div><div>Hello world</div>");
Pattern pattern = Pattern.compile("hElLo \"world\"", Pattern.CASE_INSENSITIVE);
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(pattern)).textContent());
}
@Test
void shouldSupportHasLocator() {
page.setContent("<div><span>hello</span></div><div><span>world</span></div>");
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("text=world")))).hasCount(1);
assertEquals("<div><span>world</span></div>", page.locator("div", new Page.LocatorOptions().setHas(page.locator("text=world"))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("text='hello'")))).hasCount(1);
assertEquals("<div><span>hello</span></div>", page.locator("div", new Page.LocatorOptions().setHas(page.locator("text='hello'"))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("xpath=./span")))).hasCount(2);
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("span")))).hasCount(2);
assertThat(page.locator("div", new Page.LocatorOptions().setHas(page.locator("span", new Page.LocatorOptions().setHasText("wor"))))).hasCount(1);
assertEquals("<div><span>world</span></div>", page.locator("div", new Page.LocatorOptions().setHas(
page.locator("span", new Page.LocatorOptions().setHasText("wor")))).evaluate("e => e.outerHTML"));
assertThat(page.locator("div", new Page.LocatorOptions()
.setHas(page.locator("span")).setHasText("wor"))).hasCount(1);
}
}
@@ -110,7 +110,7 @@ public class TestPageNavigate extends TestBase {
@Test
void shouldWorkWithSubframesReturn204() {
server.setRoute("/frames/frame.html", exchange -> {
exchange.sendResponseHeaders(204, 0);
exchange.sendResponseHeaders(204, -1);
exchange.getResponseBody().close();
});
page.navigate(server.PREFIX + "/frames/one-frame.html");
@@ -119,7 +119,7 @@ public class TestPageNavigate extends TestBase {
@Test
void shouldWorkWithSubframesReturn204WithDomcontentloaded() {
server.setRoute("/frames/frame.html", exchange -> {
exchange.sendResponseHeaders(204, 0);
exchange.sendResponseHeaders(204, -1);
exchange.getResponseBody().close();
});
page.navigate(server.PREFIX + "/frames/one-frame.html", new Page.NavigateOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
@@ -129,7 +129,7 @@ public class TestPageNavigate extends TestBase {
void shouldFailWhenServerReturns204() {
// WebKit just loads an empty page.
server.setRoute("/empty.html", exchange -> {
exchange.sendResponseHeaders(204, 0);
exchange.sendResponseHeaders(204, -1);
exchange.getResponseBody().close();
});
try {
@@ -24,7 +24,7 @@ import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageNetworkRequest extends TestBase {
@Test

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