Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cc79c4c41 | |||
| 43da8b23e1 | |||
| bcc240d5cb | |||
| 79668f877f | |||
| e5e9a7db99 | |||
| 0c8706f1eb |
@@ -1,10 +1,9 @@
|
||||
name: Publish
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
|
||||
@@ -3,7 +3,7 @@ name: "devrelease:docker"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
jobs:
|
||||
publish-canary-docker:
|
||||
name: "publish to DockerHub"
|
||||
|
||||
@@ -2,11 +2,11 @@ name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
jobs:
|
||||
dev:
|
||||
@@ -21,10 +21,9 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
java-version: 1.8
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
@@ -37,14 +36,9 @@ jobs:
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
run: mvn test --no-transfer-progress
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
- name: Run tracing tests w/ sources
|
||||
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
PLAYWRIGHT_JAVA_SRC: src/test/java
|
||||
- name: Test Spring Boot Starter
|
||||
shell: bash
|
||||
env:
|
||||
@@ -76,10 +70,9 @@ jobs:
|
||||
shell: powershell
|
||||
run: Install-WindowsFeature Server-Media-Foundation
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
java-version: 1.8
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
@@ -92,47 +85,7 @@ jobs:
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
run: mvn test --no-transfer-progress
|
||||
env:
|
||||
BROWSER: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
|
||||
Java_17:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.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
|
||||
|
||||
@@ -2,15 +2,17 @@ name: Test CLI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -26,6 +28,3 @@ jobs:
|
||||
run: mvn install -D skipTests --no-transfer-progress
|
||||
- name: Test CLI
|
||||
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
|
||||
- name: Test CLI version
|
||||
shell: bash
|
||||
run: tools/test-cli-version/test.sh
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
- '.github/workflows/test_docker.yml'
|
||||
- 'Dockerfile*'
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -14,7 +14,7 @@ on:
|
||||
- scripts/CLI_VERSION
|
||||
- '**/pom.xml'
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
jobs:
|
||||
test:
|
||||
|
||||
@@ -2,14 +2,14 @@ name: Verify API
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
paths:
|
||||
- 'scripts/*'
|
||||
- 'api-generator/*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- release-*
|
||||
paths:
|
||||
- 'scripts/**'
|
||||
@@ -17,6 +17,8 @@ on:
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| 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 -->96.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->96.0.4641.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->92.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
|
||||
|
||||
@@ -43,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.17.0</version>
|
||||
<version>1.14.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@@ -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/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
|
||||
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
|
||||
|
||||
## Is Playwright for Java ready?
|
||||
|
||||
|
||||
@@ -5,9 +5,3 @@
|
||||
* set new driver version in `scripts/CLI_VERSION`
|
||||
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
|
||||
* commit & send PR with the roll
|
||||
|
||||
# Updating Version
|
||||
|
||||
```bash
|
||||
./scripts/set_maven_version.sh 1.15.0
|
||||
```
|
||||
|
||||
+17
-2
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<version>1.15.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
<name>Playwright - Drivers For All Platforms</name>
|
||||
<description>
|
||||
This module includes Playwright driver and related utilities for all supported platforms.
|
||||
This module includes playwright-cli binary and related utilities for all supported platforms.
|
||||
It is intended to be used on the systems where Playwright driver is not preinstalled.
|
||||
</description>
|
||||
|
||||
@@ -28,10 +28,25 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -28,22 +28,15 @@ public class DriverJar extends Driver {
|
||||
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
|
||||
private final Path driverTempDir;
|
||||
|
||||
public DriverJar() throws IOException {
|
||||
// Allow specifying custom path for the driver installation
|
||||
// See https://github.com/microsoft/playwright-java/issues/728
|
||||
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
|
||||
String prefix = "playwright-java-";
|
||||
driverTempDir = alternativeTmpdir == null
|
||||
? Files.createTempDirectory(prefix)
|
||||
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
|
||||
DriverJar() throws IOException, URISyntaxException, InterruptedException {
|
||||
driverTempDir = Files.createTempDirectory("playwright-java-");
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
protected void initialize(Map<String, String> env) throws Exception {
|
||||
extractDriverToTempDir();
|
||||
if (installBrowsers)
|
||||
installBrowsers(env);
|
||||
installBrowsers(env);
|
||||
}
|
||||
|
||||
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
|
||||
@@ -55,13 +48,13 @@ public class DriverJar extends Driver {
|
||||
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
|
||||
return;
|
||||
}
|
||||
Path driver = driverPath();
|
||||
String cliFileName = super.cliFileName();
|
||||
Path driver = driverTempDir.resolve(cliFileName);
|
||||
if (!Files.exists(driver)) {
|
||||
throw new RuntimeException("Failed to find driver: " + driver);
|
||||
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
|
||||
}
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
|
||||
pb.environment().putAll(env);
|
||||
setRequiredEnvironmentVariables(pb);
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -138,7 +131,7 @@ public class DriverJar extends Driver {
|
||||
private static String platformDir() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
if (name.contains("windows")) {
|
||||
return "win32_x64";
|
||||
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
|
||||
}
|
||||
if (name.contains("linux")) {
|
||||
return "linux";
|
||||
|
||||
@@ -17,30 +17,22 @@
|
||||
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 {
|
||||
@BeforeEach
|
||||
void clearSystemProperties() {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
System.clearProperty("playwright.driver.tmpdir");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightCliInstalled() throws Exception {
|
||||
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap());
|
||||
assertTrue(Files.exists(cli));
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
|
||||
@@ -50,11 +42,4 @@ public class TestInstall {
|
||||
boolean result = p.waitFor(1, TimeUnit.MINUTES);
|
||||
assertTrue(result, "Timed out waiting for browsers to install");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
|
||||
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
|
||||
DriverJar driver = new DriverJar();
|
||||
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<version>1.15.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
<name>Playwright - Driver</name>
|
||||
<description>
|
||||
This module provides API for discovery and launching of Playwright driver.
|
||||
This module provides API for discovery and launching of playwright-cli binary.
|
||||
</description>
|
||||
|
||||
<build>
|
||||
@@ -24,10 +24,25 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -35,7 +35,7 @@ public abstract class Driver {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
|
||||
protected void initialize(Map<String, String> env) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -45,45 +45,24 @@ public abstract class Driver {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = createDriver();
|
||||
instance.initialize(env, installBrowsers);
|
||||
instance.initialize(env);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
return instance.driverPath();
|
||||
String name = instance.cliFileName();
|
||||
return instance.driverDir().resolve(name);
|
||||
}
|
||||
|
||||
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
|
||||
protected abstract void initialize(Map<String, String> env) throws Exception;
|
||||
|
||||
public Path driverPath() {
|
||||
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
protected String cliFileName() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
"playwright.cmd" : "playwright.sh";
|
||||
return driverDir().resolve(cliFileName);
|
||||
}
|
||||
|
||||
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
|
||||
pb.environment().put("PW_LANG_NAME", "java");
|
||||
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
|
||||
String version = Driver.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMajorJavaVersion() {
|
||||
String version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.")) {
|
||||
return version.substring(2, 3);
|
||||
}
|
||||
int dot = version.indexOf(".");
|
||||
if (dot != -1) {
|
||||
return version.substring(0, dot);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private static Driver createDriver() throws Exception {
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<version>1.15.2</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.17.0</version>
|
||||
<version>1.11.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"catalogs": {},
|
||||
"aliases": {
|
||||
"playwright": {
|
||||
"script-ref": "scripts/playwright.java",
|
||||
"description": "Playwright lets you automate Chromium, Firefox and Webkit with a single API. \nWith this cli you can install, trace, generate pdf and screenshots and more.\nExample on how to record and run a script:\n```\n jbang playwright@microsoft/playwright-java codegen -o Example.java`\n jbang --deps com.microsoft.playwright:playwright:RELEASE Example.java\n```"
|
||||
}
|
||||
},
|
||||
"templates": {}
|
||||
}
|
||||
+11
-22
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<version>1.15.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
@@ -31,7 +31,17 @@
|
||||
<configuration>
|
||||
<subpackages>com.microsoft.playwright</subpackages>
|
||||
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
|
||||
<additionalOptions>--allow-script-in-comments</additionalOptions>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -41,24 +51,7 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
<targetPath>resources</targetPath>
|
||||
</testResource>
|
||||
</testResources>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -73,10 +66,6 @@
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.opentest4j</groupId>
|
||||
<artifactId>opentest4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Exposes API that can be used for the Web API testing. 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);
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
|
||||
* environment or the service to your e2e test. 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);
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code APIResponse} class represents responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar
|
||||
* methods.
|
||||
*/
|
||||
public interface APIResponse {
|
||||
/**
|
||||
* Returns the buffer with response body.
|
||||
*/
|
||||
byte[] body();
|
||||
/**
|
||||
* Disposes the body of this response. If not called then the body will stay in memory until the context closes.
|
||||
*/
|
||||
void dispose();
|
||||
/**
|
||||
* An object with all the response HTTP headers associated with this response.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
|
||||
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
|
||||
*/
|
||||
List<HttpHeader> headersArray();
|
||||
/**
|
||||
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
|
||||
*/
|
||||
boolean ok();
|
||||
/**
|
||||
* Contains the status code of the response (e.g., 200 for a success).
|
||||
*/
|
||||
int status();
|
||||
/**
|
||||
* Contains the status text of the response (e.g. usually an "OK" for a success).
|
||||
*/
|
||||
String statusText();
|
||||
/**
|
||||
* Returns the text representation of response body.
|
||||
*/
|
||||
String text();
|
||||
/**
|
||||
* Contains the URL of the response.
|
||||
*/
|
||||
String url();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public interface Browser extends AutoCloseable {
|
||||
|
||||
class NewContextOptions {
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
@@ -69,8 +69,6 @@ public interface Browser extends AutoCloseable {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
@@ -207,7 +205,7 @@ public interface Browser extends AutoCloseable {
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
@@ -222,8 +220,6 @@ public interface Browser extends AutoCloseable {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewContextOptions setBaseURL(String baseURL) {
|
||||
@@ -485,7 +481,7 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
class NewPageOptions {
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
@@ -497,8 +493,6 @@ public interface Browser extends AutoCloseable {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
@@ -635,7 +629,7 @@ public interface Browser extends AutoCloseable {
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
@@ -650,8 +644,6 @@ public interface Browser extends AutoCloseable {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewPageOptions setBaseURL(String baseURL) {
|
||||
|
||||
@@ -29,8 +29,8 @@ import java.util.regex.Pattern;
|
||||
* <p> If a page opens another page, e.g. with a {@code window.open} call, the popup will belong to the parent page's browser
|
||||
* context.
|
||||
*
|
||||
* <p> Playwright allows creating "incognito" browser contexts with {@link Browser#newContext Browser.newContext()} method.
|
||||
* "Incognito" browser contexts don't write any browsing data to disk.
|
||||
* <p> Playwright allows creation of "incognito" browser contexts with {@code browser.newContext()} method. "Incognito" browser
|
||||
* contexts don't write any browsing data to disk.
|
||||
* <pre>{@code
|
||||
* // Create a new incognito browser context
|
||||
* BrowserContext context = browser.newContext();
|
||||
@@ -547,10 +547,6 @@ 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 true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
@@ -387,8 +387,6 @@ public interface BrowserType {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
@@ -583,7 +581,7 @@ public interface BrowserType {
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
@@ -606,8 +604,6 @@ public interface BrowserType {
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public LaunchPersistentContextOptions setBaseURL(String baseURL) {
|
||||
@@ -1083,8 +1079,7 @@ public interface BrowserType {
|
||||
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
|
||||
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
|
||||
* empty string to use a temporary directory instead.
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||
*/
|
||||
default BrowserContext launchPersistentContext(Path userDataDir) {
|
||||
return launchPersistentContext(userDataDir, null);
|
||||
@@ -1098,8 +1093,7 @@ public interface BrowserType {
|
||||
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
|
||||
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
|
||||
* empty string to use a temporary directory instead.
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||
*/
|
||||
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
|
||||
/**
|
||||
|
||||
@@ -29,13 +29,11 @@ import static java.util.Arrays.asList;
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
pb.command().addAll(asList(args));
|
||||
Driver.setRequiredEnvironmentVariables(pb);
|
||||
String version = Playwright.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
|
||||
pb.environment().put("PW_CLI_TARGET_LANG", "java");
|
||||
}
|
||||
pb.inheritIO();
|
||||
Process process = pb.start();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.microsoft.playwright;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
|
||||
@@ -39,6 +40,10 @@ import java.nio.file.Path;
|
||||
* // 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,8 +23,6 @@ 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();
|
||||
@@ -36,6 +34,10 @@ 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,6 +18,7 @@ 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,7 +813,6 @@ 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;
|
||||
@@ -842,7 +841,6 @@ 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) {
|
||||
@@ -1217,47 +1215,6 @@ 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.
|
||||
@@ -1498,7 +1455,6 @@ 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;
|
||||
@@ -1519,7 +1475,6 @@ 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) {
|
||||
@@ -1920,9 +1875,7 @@ public interface Frame {
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public Object url;
|
||||
/**
|
||||
@@ -1931,7 +1884,6 @@ 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;
|
||||
@@ -1947,27 +1899,21 @@ public interface Frame {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(Pattern url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(Predicate<String> url) {
|
||||
this.url = url;
|
||||
@@ -1979,7 +1925,6 @@ 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) {
|
||||
@@ -2059,7 +2004,6 @@ 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;
|
||||
@@ -2080,7 +2024,6 @@ 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) {
|
||||
@@ -2370,9 +2313,6 @@ 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.
|
||||
@@ -2400,9 +2340,6 @@ 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.
|
||||
@@ -2429,9 +2366,6 @@ 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.
|
||||
@@ -2457,9 +2391,6 @@ 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.
|
||||
@@ -2484,9 +2415,6 @@ 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.
|
||||
@@ -2531,7 +2459,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.evaluate("document.body");
|
||||
* ElementHandle bodyHandle = frame.querySelector("body");
|
||||
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -2566,7 +2494,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.evaluate("document.body");
|
||||
* ElementHandle bodyHandle = frame.querySelector("body");
|
||||
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -2709,19 +2637,6 @@ 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.
|
||||
*
|
||||
@@ -2996,18 +2911,7 @@ 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.
|
||||
*/
|
||||
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);
|
||||
Locator locator(String selector);
|
||||
/**
|
||||
* Returns frame's name attribute as specified in the tag.
|
||||
*
|
||||
@@ -3077,8 +2981,6 @@ 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}.
|
||||
@@ -3092,8 +2994,6 @@ 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}.
|
||||
@@ -3105,8 +3005,6 @@ 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.
|
||||
@@ -3977,9 +3875,6 @@ 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.
|
||||
@@ -4015,9 +3910,6 @@ 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.
|
||||
@@ -4063,9 +3955,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(String url) {
|
||||
waitForURL(url, null);
|
||||
@@ -4077,9 +3967,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(String url, WaitForURLOptions options);
|
||||
/**
|
||||
@@ -4089,9 +3977,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Pattern url) {
|
||||
waitForURL(url, null);
|
||||
@@ -4103,9 +3989,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Pattern url, WaitForURLOptions options);
|
||||
/**
|
||||
@@ -4115,9 +3999,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Predicate<String> url) {
|
||||
waitForURL(url, null);
|
||||
@@ -4129,9 +4011,7 @@ public interface Frame {
|
||||
* frame.waitForURL("**\/target.html");
|
||||
* }</pre>
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Predicate<String> url, WaitForURLOptions options);
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
|
||||
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
|
||||
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
|
||||
* <pre>{@code
|
||||
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
|
||||
* locator.click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> **Strictness**
|
||||
*
|
||||
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
|
||||
* given selector.
|
||||
* <pre>{@code
|
||||
* // Throws if there are several frames in DOM:
|
||||
* page.frame_locator(".result-frame").locator("button").click();
|
||||
*
|
||||
* // Works because we explicitly tell locator to pick the first frame:
|
||||
* page.frame_locator(".result-frame").first().locator("button").click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> **Converting Locator to FrameLocator**
|
||||
*
|
||||
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/:scope">{@code :scope}</a> CSS selector:
|
||||
* <pre>{@code
|
||||
* Locator frameLocator = locator.frameLocator(':scope');
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FrameLocator {
|
||||
class LocatorOptions {
|
||||
/**
|
||||
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
|
||||
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
|
||||
*
|
||||
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
|
||||
*/
|
||||
public Locator has;
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. 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,6 +17,7 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
|
||||
|
||||
@@ -19,14 +19,49 @@ package com.microsoft.playwright;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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>
|
||||
*
|
||||
* <p> <a href="https://playwright.dev/java/docs/locators/">Learn more about locators</a>.
|
||||
* <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>
|
||||
*/
|
||||
public interface Locator {
|
||||
class BoundingBoxOptions {
|
||||
@@ -389,107 +424,6 @@ 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
|
||||
@@ -863,47 +797,6 @@ 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.
|
||||
@@ -1462,51 +1355,6 @@ public interface Locator {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class WaitForOptions {
|
||||
/**
|
||||
* Defaults to {@code "visible"}. Can be either:
|
||||
* <ul>
|
||||
* <li> {@code "attached"} - wait for element to be present in DOM.</li>
|
||||
* <li> {@code "detached"} - wait for element to not be present in DOM.</li>
|
||||
* <li> {@code "visible"} - wait for element to have non-empty bounding box and no {@code visibility:hidden}. Note that element without any
|
||||
* content or with {@code display:none} has an empty bounding box and is not considered visible.</li>
|
||||
* <li> {@code "hidden"} - wait for element to be either detached from DOM, or have an empty bounding box or {@code visibility:hidden}. This
|
||||
* is opposite to the {@code "visible"} option.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public WaitForSelectorState state;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Defaults to {@code "visible"}. Can be either:
|
||||
* <ul>
|
||||
* <li> {@code "attached"} - wait for element to be present in DOM.</li>
|
||||
* <li> {@code "detached"} - wait for element to not be present in DOM.</li>
|
||||
* <li> {@code "visible"} - wait for element to have non-empty bounding box and no {@code visibility:hidden}. Note that element without any
|
||||
* content or with {@code display:none} has an empty bounding box and is not considered visible.</li>
|
||||
* <li> {@code "hidden"} - wait for element to be either detached from DOM, or have an empty bounding box or {@code visibility:hidden}. This
|
||||
* is opposite to the {@code "visible"} option.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public WaitForOptions setState(WaitForSelectorState state) {
|
||||
this.state = state;
|
||||
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 WaitForOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns an array of {@code node.innerText} values for all matching nodes.
|
||||
*/
|
||||
@@ -1781,20 +1629,6 @@ 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.
|
||||
@@ -2019,18 +1853,6 @@ 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.
|
||||
*
|
||||
@@ -2178,29 +2000,17 @@ public interface Locator {
|
||||
*/
|
||||
Locator last();
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the {@code Locator}'s subtree.
|
||||
* 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.
|
||||
*
|
||||
* @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 {@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);
|
||||
Locator locator(String selector);
|
||||
/**
|
||||
* 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()}.
|
||||
*
|
||||
@@ -2857,29 +2667,5 @@ public interface Locator {
|
||||
* zero timeout disables this.
|
||||
*/
|
||||
void uncheck(UncheckOptions options);
|
||||
/**
|
||||
* Returns when element specified by locator satisfies the {@code state} option.
|
||||
*
|
||||
* <p> If target element already satisfies the condition, the method returns immediately. Otherwise, waits for up to {@code timeout}
|
||||
* milliseconds until the condition is met.
|
||||
* <pre>{@code
|
||||
* Locator orderSent = page.locator("#order-sent");
|
||||
* orderSent.waitFor();
|
||||
* }</pre>
|
||||
*/
|
||||
default void waitFor() {
|
||||
waitFor(null);
|
||||
}
|
||||
/**
|
||||
* Returns when element specified by locator satisfies the {@code state} option.
|
||||
*
|
||||
* <p> If target element already satisfies the condition, the method returns immediately. Otherwise, waits for up to {@code timeout}
|
||||
* milliseconds until the condition is met.
|
||||
* <pre>{@code
|
||||
* Locator orderSent = page.locator("#order-sent");
|
||||
* orderSent.waitFor();
|
||||
* }</pre>
|
||||
*/
|
||||
void waitFor(WaitForOptions options);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
|
||||
|
||||
@@ -147,6 +147,10 @@ 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);
|
||||
/**
|
||||
@@ -1143,7 +1147,6 @@ 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;
|
||||
@@ -1164,7 +1167,6 @@ 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) {
|
||||
@@ -1186,7 +1188,6 @@ 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;
|
||||
@@ -1207,7 +1208,6 @@ 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,7 +1234,6 @@ 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;
|
||||
@@ -1263,7 +1262,6 @@ 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) {
|
||||
@@ -1638,47 +1636,6 @@ 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}.
|
||||
@@ -1932,7 +1889,6 @@ 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;
|
||||
@@ -1953,7 +1909,6 @@ 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) {
|
||||
@@ -2240,7 +2195,6 @@ 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;
|
||||
@@ -2261,7 +2215,6 @@ 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) {
|
||||
@@ -2759,9 +2712,7 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public Object url;
|
||||
/**
|
||||
@@ -2770,7 +2721,6 @@ 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;
|
||||
@@ -2786,27 +2736,21 @@ public interface Page extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(Pattern url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
public WaitForNavigationOptions setUrl(Predicate<String> url) {
|
||||
this.url = url;
|
||||
@@ -2818,7 +2762,6 @@ 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) {
|
||||
@@ -2986,7 +2929,6 @@ 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;
|
||||
@@ -3007,7 +2949,6 @@ 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) {
|
||||
@@ -3504,10 +3445,7 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
void emulateMedia(EmulateMediaOptions options);
|
||||
/**
|
||||
* <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
|
||||
* 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
|
||||
@@ -3533,10 +3471,7 @@ public interface Page extends AutoCloseable {
|
||||
return evalOnSelector(selector, expression, arg, null);
|
||||
}
|
||||
/**
|
||||
* <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
|
||||
* 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
|
||||
@@ -3561,10 +3496,7 @@ public interface Page extends AutoCloseable {
|
||||
return evalOnSelector(selector, expression, null);
|
||||
}
|
||||
/**
|
||||
* <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
|
||||
* 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
|
||||
@@ -3588,10 +3520,7 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
Object evalOnSelector(String selector, String expression, Object arg, EvalOnSelectorOptions options);
|
||||
/**
|
||||
* <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
|
||||
* 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
|
||||
@@ -3612,10 +3541,7 @@ public interface Page extends AutoCloseable {
|
||||
return evalOnSelectorAll(selector, expression, null);
|
||||
}
|
||||
/**
|
||||
* <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
|
||||
* 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
|
||||
@@ -3660,7 +3586,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.evaluate("document.body");
|
||||
* ElementHandle bodyHandle = page.querySelector("body");
|
||||
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -3699,7 +3625,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.evaluate("document.body");
|
||||
* ElementHandle bodyHandle = page.querySelector("body");
|
||||
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -4045,19 +3971,6 @@ 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.
|
||||
*/
|
||||
@@ -4383,20 +4296,7 @@ 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.
|
||||
*/
|
||||
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);
|
||||
Locator locator(String selector);
|
||||
/**
|
||||
* The page's main frame. Page is guaranteed to have a main frame which persists during navigations.
|
||||
*/
|
||||
@@ -4600,10 +4500,9 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
void press(String selector, String key, PressOptions options);
|
||||
/**
|
||||
* <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()}.
|
||||
* 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()}.
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}.
|
||||
*
|
||||
@@ -4614,10 +4513,9 @@ public interface Page extends AutoCloseable {
|
||||
return querySelector(selector, null);
|
||||
}
|
||||
/**
|
||||
* <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()}.
|
||||
* 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()}.
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}.
|
||||
*
|
||||
@@ -4626,9 +4524,7 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
ElementHandle querySelector(String selector, QuerySelectorOptions options);
|
||||
/**
|
||||
* <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
|
||||
* 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()}.
|
||||
@@ -4638,21 +4534,17 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
List<ElementHandle> querySelectorAll(String selector);
|
||||
/**
|
||||
* 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.
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
@@ -5562,9 +5454,7 @@ public interface Page extends AutoCloseable {
|
||||
* Browser#newContext Browser.newContext()} allows to set viewport size (and more) for all pages in the context at once.
|
||||
*
|
||||
* <p> {@code page.setViewportSize} will resize the page. A lot of websites don't expect phones to change size, so you should set the
|
||||
* viewport size before navigating to the page. {@link Page#setViewportSize Page.setViewportSize()} will also reset
|
||||
* {@code screen} size, use {@link Browser#newContext Browser.newContext()} with {@code screen} and {@code viewport} parameters if you need
|
||||
* better control of these properties.
|
||||
* viewport size before navigating to the page.
|
||||
* <pre>{@code
|
||||
* Page page = browser.newPage();
|
||||
* page.setViewportSize(640, 480);
|
||||
@@ -6416,9 +6306,6 @@ 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.
|
||||
@@ -6454,9 +6341,6 @@ 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.
|
||||
@@ -6510,9 +6394,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(String url) {
|
||||
waitForURL(url, null);
|
||||
@@ -6526,9 +6408,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(String url, WaitForURLOptions options);
|
||||
/**
|
||||
@@ -6540,9 +6420,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Pattern url) {
|
||||
waitForURL(url, null);
|
||||
@@ -6556,9 +6434,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Pattern url, WaitForURLOptions options);
|
||||
/**
|
||||
@@ -6570,9 +6446,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
default void waitForURL(Predicate<String> url) {
|
||||
waitForURL(url, null);
|
||||
@@ -6586,9 +6460,7 @@ public interface Page extends AutoCloseable {
|
||||
*
|
||||
* <p> Shortcut for main frame's {@link Frame#waitForURL Frame.waitForURL()}.
|
||||
*
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
|
||||
* parameter is a string without wilcard characters, the method will wait for navigation to URL that is exactly equal to
|
||||
* the string.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
|
||||
*/
|
||||
void waitForURL(Predicate<String> url, WaitForURLOptions options);
|
||||
/**
|
||||
|
||||
@@ -64,10 +64,6 @@ 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,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@@ -100,11 +101,6 @@ public interface Route {
|
||||
* is resolved relative to the current working directory.
|
||||
*/
|
||||
public Path path;
|
||||
/**
|
||||
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
|
||||
* using fulfill options.
|
||||
*/
|
||||
public APIResponse response;
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
*/
|
||||
@@ -146,14 +142,6 @@ public interface Route {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
|
||||
* using fulfill options.
|
||||
*/
|
||||
public FulfillOptions setResponse(APIResponse response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Selectors can be used to install custom selector engines. See <a
|
||||
@@ -61,11 +62,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -96,11 +97,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -129,11 +130,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -164,11 +165,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* Locator button = page.locator("tag=button");
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
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
|
||||
@@ -47,22 +48,9 @@ public interface Tracing {
|
||||
*/
|
||||
public Boolean screenshots;
|
||||
/**
|
||||
* If this option is true tracing will
|
||||
* <ul>
|
||||
* <li> capture DOM snapshot on every action</li>
|
||||
* <li> record network activity</li>
|
||||
* </ul>
|
||||
* Whether to capture DOM snapshot on every action.
|
||||
*/
|
||||
public Boolean snapshots;
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
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
|
||||
@@ -80,45 +68,12 @@ public interface Tracing {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If this option is true tracing will
|
||||
* <ul>
|
||||
* <li> capture DOM snapshot on every action</li>
|
||||
* <li> record network activity</li>
|
||||
* </ul>
|
||||
* Whether to capture DOM snapshot on every action.
|
||||
*/
|
||||
public StartOptions setSnapshots(boolean snapshots) {
|
||||
this.snapshots = snapshots;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
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 {
|
||||
/**
|
||||
@@ -202,34 +157,7 @@ public interface Tracing {
|
||||
* .setPath(Paths.get("trace2.zip")));
|
||||
* }</pre>
|
||||
*/
|
||||
default void startChunk() {
|
||||
startChunk(null);
|
||||
}
|
||||
/**
|
||||
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
|
||||
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
|
||||
* {@link Tracing#stopChunk Tracing.stopChunk()}.
|
||||
* <pre>{@code
|
||||
* context.tracing().start(new Tracing.StartOptions()
|
||||
* .setScreenshots(true)
|
||||
* .setSnapshots(true));
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://playwright.dev");
|
||||
*
|
||||
* context.tracing().startChunk();
|
||||
* page.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);
|
||||
void startChunk();
|
||||
/**
|
||||
* Stop tracing.
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.assertions;
|
||||
|
||||
|
||||
/**
|
||||
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code APIResponse}
|
||||
* in the tests. A new instance of {@code APIResponseAssertions} is created by calling {@link PlaywrightAssertions#assertThat
|
||||
* PlaywrightAssertions.assertThat()}:
|
||||
* <pre>{@code
|
||||
* ...
|
||||
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
*
|
||||
* public class TestPage {
|
||||
* ...
|
||||
* @Test
|
||||
* void navigatesToLoginPage() {
|
||||
* ...
|
||||
* APIResponse response = page.request().get('https://playwright.dev');
|
||||
* assertThat(response).isOK();
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public interface APIResponseAssertions {
|
||||
/**
|
||||
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
|
||||
* successful:
|
||||
* <pre>{@code
|
||||
* assertThat(response).not().isOK();
|
||||
* }</pre>
|
||||
*/
|
||||
APIResponseAssertions not();
|
||||
/**
|
||||
* Ensures the response status code is within [200..299] range.
|
||||
* <pre>{@code
|
||||
* assertThat(response).isOK();
|
||||
* }</pre>
|
||||
*/
|
||||
void isOK();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.assertions;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
|
||||
* tests. A new instance of {@code PageAssertions} is created by calling {@link PlaywrightAssertions#assertThat
|
||||
* PlaywrightAssertions.assertThat()}:
|
||||
* <pre>{@code
|
||||
* ...
|
||||
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
*
|
||||
* public class TestPage {
|
||||
* ...
|
||||
* @Test
|
||||
* void navigatesToLoginPage() {
|
||||
* ...
|
||||
* page.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);
|
||||
}
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.assertions;
|
||||
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequestContext;
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.RequestOptions;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.toFilePayload;
|
||||
|
||||
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
|
||||
private final TracingImpl tracing;
|
||||
|
||||
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse delete(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "DELETE"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
|
||||
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse fetch(Request request, RequestOptions optionsArg) {
|
||||
RequestOptionsImpl options = (RequestOptionsImpl) optionsArg;
|
||||
if (options == null) {
|
||||
options = new RequestOptionsImpl();
|
||||
}
|
||||
if (options.method == null) {
|
||||
options.method = request.method();
|
||||
}
|
||||
if (options.headers == null) {
|
||||
options.headers = request.headers();
|
||||
}
|
||||
if (options.data == null && options.form == null && options.multipart == null) {
|
||||
options.data = request.postDataBuffer();
|
||||
}
|
||||
return fetch(request.url(), options);
|
||||
}
|
||||
|
||||
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
|
||||
if (options == null) {
|
||||
options = new RequestOptionsImpl();
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("url", url);
|
||||
if (options.params != null) {
|
||||
Map<String, String> queryParams = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, ?> e : options.params.entrySet()) {
|
||||
queryParams.put(e.getKey(), "" + e.getValue());
|
||||
}
|
||||
params.add("params", toNameValueArray(queryParams));
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", toProtocol(options.headers));
|
||||
}
|
||||
|
||||
if (options.data != null) {
|
||||
byte[] bytes = null;
|
||||
if (options.data instanceof byte[]) {
|
||||
bytes = (byte[]) options.data;
|
||||
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
|
||||
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
if (bytes == null) {
|
||||
params.add("jsonData", gson().toJsonTree(options.data));
|
||||
} else {
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
}
|
||||
if (options.form != null) {
|
||||
params.add("formData", toNameValueArray(options.form.fields));
|
||||
}
|
||||
if (options.multipart != null) {
|
||||
params.add("multipartData", serializeMultipartData(options.multipart.fields));
|
||||
}
|
||||
if (options.timeout != null) {
|
||||
params.addProperty("timeout", options.timeout);
|
||||
}
|
||||
if (options.failOnStatusCode != null) {
|
||||
params.addProperty("failOnStatusCode", options.failOnStatusCode);
|
||||
}
|
||||
if (options.ignoreHTTPSErrors != null) {
|
||||
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
|
||||
}
|
||||
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
|
||||
return new APIResponseImpl(this, json.getAsJsonObject("response"));
|
||||
}
|
||||
|
||||
private static boolean isJsonContentType(Map<String, String> headers) {
|
||||
if (headers == null) {
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
if ("content-type".equalsIgnoreCase(e.getKey())) {
|
||||
return "application/json".equals(e.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static JsonArray serializeMultipartData(Map<String, Object> data) {
|
||||
JsonArray result = new JsonArray();
|
||||
for (Map.Entry<String, Object> e : data.entrySet()) {
|
||||
FilePayload filePayload = null;
|
||||
if (e.getValue() instanceof FilePayload) {
|
||||
filePayload = (FilePayload) e.getValue();
|
||||
} else if (e.getValue() instanceof Path) {
|
||||
filePayload = toFilePayload((Path) e.getValue());
|
||||
} else if (e.getValue() instanceof File) {
|
||||
filePayload = toFilePayload(((File) e.getValue()).toPath());
|
||||
}
|
||||
JsonObject item = new JsonObject();
|
||||
item.addProperty("name", e.getKey());
|
||||
if (filePayload == null) {
|
||||
item.addProperty("value", "" + e.getValue());
|
||||
} else {
|
||||
item.add("file", toProtocol(filePayload));
|
||||
}
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse get(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "GET"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse head(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "HEAD"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse patch(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "PATCH"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse post(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "POST"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse put(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "PUT"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String storageState(StorageStateOptions options) {
|
||||
return withLogging("APIRequestContext.storageState", () -> {
|
||||
JsonElement json = sendMessage("storageState");
|
||||
String storageState = json.toString();
|
||||
if (options != null && options.path != null) {
|
||||
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
|
||||
}
|
||||
return storageState;
|
||||
});
|
||||
}
|
||||
|
||||
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
|
||||
RequestOptionsImpl impl = (RequestOptionsImpl) options;
|
||||
if (impl == null) {
|
||||
impl = new RequestOptionsImpl();
|
||||
}
|
||||
if (impl.method == null) {
|
||||
impl.method = method;
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequest;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class APIRequestImpl implements APIRequest {
|
||||
private final PlaywrightImpl playwright;
|
||||
|
||||
APIRequestImpl(PlaywrightImpl playwright) {
|
||||
this.playwright = playwright;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequestContextImpl newContext(NewContextOptions options) {
|
||||
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
|
||||
}
|
||||
|
||||
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new NewContextOptions();
|
||||
}
|
||||
if (options.storageStatePath != null) {
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(options.storageStatePath);
|
||||
options.storageState = new String(bytes, StandardCharsets.UTF_8);
|
||||
options.storageStatePath = null;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read storage state from file", e);
|
||||
}
|
||||
}
|
||||
JsonObject storageState = null;
|
||||
if (options.storageState != null) {
|
||||
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
|
||||
options.storageState = null;
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (storageState != null) {
|
||||
params.add("storageState", storageState);
|
||||
}
|
||||
|
||||
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
|
||||
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.assertions.APIResponseAssertions;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class APIResponseAssertionsImpl implements APIResponseAssertions {
|
||||
private final APIResponse actual;
|
||||
private final boolean isNot;
|
||||
|
||||
APIResponseAssertionsImpl(APIResponse response, boolean isNot) {
|
||||
this.actual = response;
|
||||
this.isNot = isNot;
|
||||
}
|
||||
|
||||
public APIResponseAssertionsImpl(APIResponse response) {
|
||||
this(response, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponseAssertions not() {
|
||||
return new APIResponseAssertionsImpl(actual, !isNot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isOK() {
|
||||
if (actual.ok() == !isNot) {
|
||||
return;
|
||||
}
|
||||
String message = "Response status expected to be within [200..299] range, was " + actual.status();
|
||||
if (isNot) {
|
||||
message = message.replace("expected to", "expected not to");
|
||||
}
|
||||
List<String> logList = ((APIResponseImpl) actual).fetchLog();
|
||||
String log = String.join("\n", logList);
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + log;
|
||||
}
|
||||
throw new AssertionFailedError(message + log);
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class APIResponseImpl implements APIResponse {
|
||||
final APIRequestContextImpl context;
|
||||
private final JsonObject initializer;
|
||||
private final RawHeaders headers;
|
||||
|
||||
APIResponseImpl(APIRequestContextImpl apiRequestContext, JsonObject response) {
|
||||
context = apiRequestContext;
|
||||
initializer = response;
|
||||
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] body() {
|
||||
return context.withLogging("APIResponse.body", () -> {
|
||||
try {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
|
||||
if (!json.has("binary")) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
return Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
} catch (PlaywrightException e) {
|
||||
if (isSafeCloseError(e)) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
context.withLogging("APIResponse.dispose", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
context.sendMessage("disposeAPIResponse", params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return headers.headers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpHeader> headersArray() {
|
||||
return headers.headersArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ok() {
|
||||
int status = status();
|
||||
return status == 0 || (status >= 200 && status <= 299);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int status() {
|
||||
return initializer.get("status").getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String statusText() {
|
||||
return initializer.get("statusText").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String text() {
|
||||
return new String(body(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
String fetchUid() {
|
||||
return initializer.get("fetchUid").getAsString();
|
||||
}
|
||||
|
||||
List<String> fetchLog() {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
|
||||
JsonArray log = json.get("log").getAsJsonArray();
|
||||
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
import org.opentest4j.ValueWrapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class AssertionsBase {
|
||||
final LocatorImpl actualLocator;
|
||||
final boolean isNot;
|
||||
|
||||
AssertionsBase(LocatorImpl actual, boolean isNot) {
|
||||
this.actualLocator = actual;
|
||||
this.isNot = isNot;
|
||||
}
|
||||
|
||||
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
|
||||
expectImpl(expression, asList(textValue), expected, message, options);
|
||||
}
|
||||
|
||||
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
|
||||
if (options == null) {
|
||||
options = new FrameExpectOptions();
|
||||
}
|
||||
options.expectedText = expectedText;
|
||||
options.isNot = isNot;
|
||||
expectImpl(expression, options, expected, message);
|
||||
}
|
||||
|
||||
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
|
||||
if (expectOptions.timeout == null) {
|
||||
expectOptions.timeout = 5_000.0;
|
||||
}
|
||||
if (expectOptions.isNot) {
|
||||
message = message.replace("expected to", "expected not to");
|
||||
}
|
||||
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
|
||||
if (result.matches == isNot) {
|
||||
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
|
||||
String log = String.join("\n", result.log);
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + log;
|
||||
}
|
||||
if (expected == null) {
|
||||
throw new AssertionFailedError(message + log);
|
||||
}
|
||||
ValueWrapper expectedValue = formatValue(expected);
|
||||
ValueWrapper actualValue = formatValue(actual);
|
||||
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
|
||||
throw new AssertionFailedError(message + log, expectedValue, actualValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueWrapper formatValue(Object value) {
|
||||
if (value == null || !value.getClass().isArray()) {
|
||||
return ValueWrapper.create(value);
|
||||
}
|
||||
Collection<String> values = asList((Object[]) value).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||
String stringRepresentation = "[" + String.join(", ", values) + "]";
|
||||
return ValueWrapper.create(value, stringRepresentation);
|
||||
}
|
||||
|
||||
static ExpectedTextValue expectedRegex(Pattern pattern) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.regexSource = pattern.pattern();
|
||||
if (pattern.flags() != 0) {
|
||||
expected.regexFlags = toJsRegexFlags(pattern);
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -44,7 +47,6 @@ import static java.util.Arrays.asList;
|
||||
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final BrowserImpl browser;
|
||||
private final TracingImpl tracing;
|
||||
private final APIRequestContextImpl request;
|
||||
final List<PageImpl> pages = new ArrayList<>();
|
||||
final Router routes = new Router();
|
||||
private boolean isClosedOrClosing;
|
||||
@@ -72,9 +74,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} else {
|
||||
browser = null;
|
||||
}
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
tracing.isRemote = browser != null && browser.isRemote;
|
||||
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
|
||||
this.tracing = new TracingImpl(this);
|
||||
}
|
||||
|
||||
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<>() : Collections.singletonList(url));
|
||||
return cookies(url == null ? new ArrayList<>() : asList(url));
|
||||
}
|
||||
|
||||
private void closeImpl() {
|
||||
@@ -329,11 +329,6 @@ 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);
|
||||
@@ -429,7 +424,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracingImpl tracing() {
|
||||
public Tracing tracing() {
|
||||
return tracing;
|
||||
}
|
||||
|
||||
@@ -462,27 +457,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
withLogging("BrowserContext.unroute", () -> {
|
||||
routes.remove(matcher, handler);
|
||||
maybeDisableNetworkInterception();
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeDisableNetworkInterception() {
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
}
|
||||
|
||||
void handleRoute(Route route) {
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
route.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
sendMessage("pause");
|
||||
}
|
||||
@@ -491,7 +473,10 @@ 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());
|
||||
handleRoute(route);
|
||||
boolean handled = routes.handle(route);
|
||||
if (!handled) {
|
||||
route.resume();
|
||||
}
|
||||
} else if ("page".equals(event)) {
|
||||
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
|
||||
pages.add(page);
|
||||
|
||||
@@ -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.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
|
||||
class BrowserImpl extends ChannelOwner implements Browser {
|
||||
@@ -40,7 +40,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
boolean isRemote;
|
||||
boolean isConnectedOverWebSocket;
|
||||
private boolean isConnected = true;
|
||||
LocalUtils localUtils;
|
||||
|
||||
enum EventType {
|
||||
DISCONNECTED,
|
||||
@@ -72,6 +71,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to close browser connection", e);
|
||||
}
|
||||
notifyRemoteClosed();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -172,7 +172,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -211,7 +210,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
|
||||
private Page newPageImpl(NewPageOptions options) {
|
||||
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
|
||||
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
|
||||
PageImpl page = context.newPage();
|
||||
page.ownedContext = context;
|
||||
context.ownerPage = page;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Browser;
|
||||
@@ -24,14 +23,17 @@ import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
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);
|
||||
}
|
||||
@@ -47,9 +49,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonElement result = sendMessage("launch", params);
|
||||
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.localUtils = localUtils;
|
||||
return browser;
|
||||
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,41 +58,51 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
|
||||
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
|
||||
if (options == null) {
|
||||
options = new ConnectOptions();
|
||||
}
|
||||
// We don't use gson() here as the headers map should be serialized to a json object.
|
||||
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("wsEndpoint", wsEndpoint);
|
||||
JsonObject json = sendMessage("connect", params).getAsJsonObject();
|
||||
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
|
||||
Connection connection = new Connection(pipe);
|
||||
PlaywrightImpl playwright = connection.initializePlaywright();
|
||||
if (!playwright.initializer.has("preLaunchedBrowser")) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
try {
|
||||
Duration timeout = Duration.ofDays(1);
|
||||
Map<String, String> headers = Collections.emptyMap();
|
||||
Duration slowMo = null;
|
||||
if (options != null) {
|
||||
if (options.timeout != null) {
|
||||
timeout = Duration.ofMillis(Math.round(options.timeout));
|
||||
}
|
||||
if (options.headers != null) {
|
||||
headers = options.headers;
|
||||
}
|
||||
if (options.slowMo != null) {
|
||||
slowMo = Duration.ofMillis(options.slowMo.intValue());
|
||||
}
|
||||
}
|
||||
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
|
||||
}
|
||||
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
|
||||
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 -> {
|
||||
playwright.unregisterSelectors();
|
||||
pipe.offClose(connectionCloseListener);
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), headers, timeout, slowMo);
|
||||
Connection connection = new Connection(transport);
|
||||
PlaywrightImpl playwright = connection.initializePlaywright();
|
||||
if (!playwright.initializer.has("preLaunchedBrowser")) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
|
||||
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.isConnectedOverWebSocket = true;
|
||||
Consumer<WebSocketTransport> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
transport.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
playwright.unregisterSelectors();
|
||||
transport.offClose(connectionCloseListener);
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +124,6 @@ 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);
|
||||
@@ -180,7 +189,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.tracing().localUtils = localUtils;
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,23 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class Message {
|
||||
@@ -56,7 +62,7 @@ public class Connection {
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
private final Root root;
|
||||
private int lastId = 0;
|
||||
private final StackTraceCollector stackTraceCollector;
|
||||
private final Path srcDir;
|
||||
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
|
||||
private String apiName;
|
||||
private static final boolean isLogging;
|
||||
@@ -79,16 +85,17 @@ public class Connection {
|
||||
}
|
||||
|
||||
Connection(Transport transport) {
|
||||
if (isLogging) {
|
||||
transport = new TransportLogger(transport);
|
||||
}
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv();
|
||||
}
|
||||
|
||||
boolean isCollectingStacks() {
|
||||
return stackTraceCollector != null;
|
||||
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
if (srcRoot == null) {
|
||||
srcDir = null;
|
||||
} else {
|
||||
srcDir = Paths.get(srcRoot);
|
||||
if (!Files.exists(srcDir)) {
|
||||
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
@@ -109,6 +116,45 @@ public class Connection {
|
||||
return internalSendMessage(guid, method, params);
|
||||
}
|
||||
|
||||
private String sourceFile(StackTraceElement frame) {
|
||||
String pkg = frame.getClassName();
|
||||
int lastDot = pkg.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
pkg = "";
|
||||
} else {
|
||||
pkg = frame.getClassName().substring(0, lastDot + 1);
|
||||
}
|
||||
pkg = pkg.replace('.', File.separatorChar);
|
||||
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
|
||||
}
|
||||
|
||||
private JsonArray currentStackTrace() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
|
||||
int index = 0;
|
||||
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
|
||||
index++;
|
||||
};
|
||||
// Find Playwright API call
|
||||
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
|
||||
// hack for tests
|
||||
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
JsonArray jsonStack = new JsonArray();
|
||||
for (; index < stack.length; index++) {
|
||||
StackTraceElement frame = stack[index];
|
||||
JsonObject jsonFrame = new JsonObject();
|
||||
jsonFrame.addProperty("file", sourceFile(frame));
|
||||
jsonFrame.addProperty("line", frame.getLineNumber());
|
||||
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
|
||||
jsonStack.add(jsonFrame);
|
||||
}
|
||||
return jsonStack;
|
||||
}
|
||||
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
|
||||
int id = ++lastId;
|
||||
WaitableResult<JsonElement> result = new WaitableResult<>();
|
||||
@@ -119,18 +165,18 @@ public class Connection {
|
||||
message.addProperty("method", method);
|
||||
message.add("params", params);
|
||||
JsonObject metadata = new JsonObject();
|
||||
if (apiName == null) {
|
||||
metadata.addProperty("internal", true);
|
||||
} else {
|
||||
if (srcDir != null) {
|
||||
metadata.add("stack", currentStackTrace());
|
||||
}
|
||||
if (apiName != null) {
|
||||
metadata.addProperty("apiName", apiName);
|
||||
// All but first message in an API call are considered internal and will be hidden from the inspector.
|
||||
apiName = null;
|
||||
if (stackTraceCollector != null) {
|
||||
metadata.add("stack", stackTraceCollector.currentStackTrace());
|
||||
}
|
||||
}
|
||||
message.add("metadata", metadata);
|
||||
transport.send(message);
|
||||
String messageString = gson().toJson(message);
|
||||
if (isLogging) {
|
||||
logWithTimestamp("SEND ► " + messageString);
|
||||
}
|
||||
transport.send(messageString);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -154,13 +200,16 @@ public class Connection {
|
||||
}
|
||||
|
||||
void processOneMessage() {
|
||||
JsonObject message = transport.poll(Duration.ofMillis(10));
|
||||
if (message == null) {
|
||||
String messageString = transport.poll(Duration.ofMillis(10));
|
||||
if (messageString == null) {
|
||||
return;
|
||||
}
|
||||
if (isLogging) {
|
||||
logWithTimestamp("◀ RECV " + messageString);
|
||||
}
|
||||
Gson gson = gson();
|
||||
Message messageObj = gson.fromJson(message, Message.class);
|
||||
dispatch(messageObj);
|
||||
Message message = gson.fromJson(messageString, Message.class);
|
||||
dispatch(message);
|
||||
}
|
||||
|
||||
private void dispatch(Message message) {
|
||||
@@ -256,9 +305,9 @@ public class Connection {
|
||||
case "ElementHandle":
|
||||
result = new ElementHandleImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "APIRequestContext":
|
||||
case "FetchRequest":
|
||||
// Create fake object as this API is experimental an only exposed in Node.js.
|
||||
result = new APIRequestContextImpl(parent, type, guid, initializer);
|
||||
result = new ChannelOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Frame":
|
||||
result = new FrameImpl(parent, type, guid, initializer);
|
||||
@@ -266,12 +315,6 @@ public class Connection {
|
||||
case "JSHandle":
|
||||
result = new JSHandleImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
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;
|
||||
@@ -293,9 +336,6 @@ 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,6 +20,7 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.ElementHandle;
|
||||
import com.microsoft.playwright.FileChooser;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import com.microsoft.playwright.options.ElementState;
|
||||
@@ -33,7 +34,7 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
|
||||
@@ -434,9 +435,9 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
@Override
|
||||
public void setChecked(boolean checked, SetCheckedOptions options) {
|
||||
if (checked) {
|
||||
check(convertType(options, CheckOptions.class));
|
||||
check(convertViaJson(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheck(convertType(options, UncheckOptions.class));
|
||||
uncheck(convertViaJson(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.microsoft.playwright.options.FilePayload;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
class FileChooserImpl implements FileChooser {
|
||||
private final PageImpl page;
|
||||
@@ -69,6 +69,6 @@ class FileChooserImpl implements FileChooser {
|
||||
@Override
|
||||
public void setFiles(FilePayload[] files, SetFilesOptions options) {
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FormDataImpl implements FormData {
|
||||
Map<String, Object> fields = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public FormData set(String name, String value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, boolean value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, int value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, Path value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, FilePayload value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -31,16 +31,16 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.options.WaitUntilState.*;
|
||||
import static com.microsoft.playwright.options.LoadState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
public class FrameImpl extends ChannelOwner implements Frame {
|
||||
private String name;
|
||||
private String url;
|
||||
FrameImpl parentFrame;
|
||||
Set<FrameImpl> childFrames = new LinkedHashSet<>();
|
||||
private final Set<WaitUntilState> loadStates = new HashSet<>();
|
||||
private final Set<LoadState> loadStates = new HashSet<>();
|
||||
|
||||
enum InternalEventType { NAVIGATED, LOADSTATE }
|
||||
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
|
||||
@@ -61,12 +61,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
}
|
||||
|
||||
private static WaitUntilState loadStateFromProtocol(String value) {
|
||||
private static LoadState loadStateFromProtocol(String value) {
|
||||
switch (value) {
|
||||
case "load": return LOAD;
|
||||
case "domcontentloaded": return DOMCONTENTLOADED;
|
||||
case "networkidle": return NETWORKIDLE;
|
||||
case "commit": return COMMIT;
|
||||
default: throw new PlaywrightException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
@@ -358,11 +357,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(this, selector);
|
||||
}
|
||||
|
||||
ElementHandle frameElementImpl() {
|
||||
JsonObject json = sendMessage("frameElement").getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
|
||||
@@ -566,8 +560,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(this, selector, convertType(options, Locator.LocatorOptions.class));
|
||||
public Locator locator(String selector) {
|
||||
return new LocatorImpl(this, selector);
|
||||
}
|
||||
|
||||
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
|
||||
@@ -586,7 +580,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageImpl page() {
|
||||
public Page page() {
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -656,9 +650,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
|
||||
if (checked) {
|
||||
checkImpl(selector, convertType(options, CheckOptions.class));
|
||||
checkImpl(selector, convertViaJson(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheckImpl(selector, convertType(options, UncheckOptions.class));
|
||||
uncheckImpl(selector, convertViaJson(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,10 +801,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
|
||||
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options);
|
||||
}
|
||||
|
||||
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForLoadStateOptions();
|
||||
}
|
||||
@@ -825,11 +815,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
runUntil(() -> {}, new WaitableRace<>(waitables));
|
||||
}
|
||||
|
||||
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
|
||||
private final WaitUntilState expectedState;
|
||||
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
|
||||
private final LoadState expectedState;
|
||||
private boolean isDone;
|
||||
|
||||
WaitForLoadStateHelper(WaitUntilState state) {
|
||||
WaitForLoadStateHelper(LoadState state) {
|
||||
expectedState = state;
|
||||
isDone = loadStates.contains(state);
|
||||
if (!isDone) {
|
||||
@@ -838,7 +828,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(WaitUntilState state) {
|
||||
public void accept(LoadState state) {
|
||||
if (expectedState.equals(state)) {
|
||||
isDone = true;
|
||||
dispose();
|
||||
@@ -861,13 +851,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
|
||||
private final UrlMatcher matcher;
|
||||
private final WaitUntilState expectedLoadState;
|
||||
private final LoadState expectedLoadState;
|
||||
private WaitForLoadStateHelper loadStateHelper;
|
||||
|
||||
private RequestImpl request;
|
||||
private RuntimeException exception;
|
||||
|
||||
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
|
||||
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
|
||||
this.matcher = matcher;
|
||||
this.expectedLoadState = expectedLoadState;
|
||||
internalListeners.add(InternalEventType.NAVIGATED, this);
|
||||
@@ -945,7 +935,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
if (matcher == null) {
|
||||
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
|
||||
}
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||
@@ -958,16 +948,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
|
||||
return waitForSelectorImpl(selector, options, false);
|
||||
}
|
||||
|
||||
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options, boolean omitReturnValue) {
|
||||
if (options == null) {
|
||||
options = new WaitForSelectorOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
params.addProperty("omitReturnValue", omitReturnValue);
|
||||
JsonElement json = sendMessage("waitForSelector", params);
|
||||
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
|
||||
if (element == null) {
|
||||
@@ -982,9 +967,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
void waitForTimeoutImpl(double timeout) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("timeout", timeout);
|
||||
sendMessage("waitForTimeout", params);
|
||||
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
|
||||
@Override
|
||||
public Void get() {
|
||||
// Override to not throw.
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1011,24 +1000,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
options = new WaitForURLOptions();
|
||||
}
|
||||
if (matcher.test(url())) {
|
||||
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
|
||||
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
|
||||
convertViaJson(options, WaitForLoadStateOptions.class));
|
||||
return;
|
||||
}
|
||||
waitForNavigationImpl(() -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
|
||||
}
|
||||
|
||||
int queryCount(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
|
||||
return result.get("value").getAsInt();
|
||||
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
|
||||
}
|
||||
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("loadstate".equals(event)) {
|
||||
JsonElement add = params.get("add");
|
||||
if (add != null) {
|
||||
WaitUntilState state = loadStateFromProtocol(add.getAsString());
|
||||
LoadState state = loadStateFromProtocol(add.getAsString());
|
||||
loadStates.add(state);
|
||||
internalListeners.notify(InternalEventType.LOADSTATE, state);
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.FrameLocator;
|
||||
import com.microsoft.playwright.Locator;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class FrameLocatorImpl implements FrameLocator {
|
||||
private final FrameImpl frame;
|
||||
private final String frameSelector;
|
||||
|
||||
FrameLocatorImpl(FrameImpl frame, String selector) {
|
||||
this.frame = frame;
|
||||
this.frameSelector = selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator first() {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocatorImpl frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator last() {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator nth(int index) {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class JsonPipe extends ChannelOwner implements Transport {
|
||||
private final Queue<JsonObject> incoming = new LinkedList<>();
|
||||
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
private enum EventType { CLOSE }
|
||||
private boolean isClosed;
|
||||
|
||||
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(JsonObject message) {
|
||||
checkIfClosed();
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("message", message);
|
||||
sendMessage("send", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject poll(Duration timeout) {
|
||||
Instant start = Instant.now();
|
||||
return runUntil(() -> {}, new Waitable<JsonObject>() {
|
||||
JsonObject message;
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
if (!incoming.isEmpty()) {
|
||||
message = incoming.remove();
|
||||
return true;
|
||||
}
|
||||
checkIfClosed();
|
||||
if (Duration.between(start, Instant.now()).compareTo(timeout) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject get() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!isClosed) {
|
||||
sendMessage("close");
|
||||
}
|
||||
}
|
||||
|
||||
void onClose(Consumer<JsonPipe> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
void offClose(Consumer<JsonPipe> handler) {
|
||||
listeners.remove(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("message".equals(event)) {
|
||||
incoming.add(params.get("message").getAsJsonObject());
|
||||
} else if ("closed".equals(event)) {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfClosed() {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Browser has been closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.assertions.LocatorAssertions;
|
||||
|
||||
import java.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.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.ElementHandle;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.JSHandle;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.SelectOption;
|
||||
@@ -12,46 +12,20 @@ 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.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
class LocatorImpl implements Locator {
|
||||
private final FrameImpl frame;
|
||||
private final String selector;
|
||||
|
||||
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
|
||||
public LocatorImpl(FrameImpl frame, String selector) {
|
||||
this.frame = frame;
|
||||
if (options != null) {
|
||||
if (options.hasText != null) {
|
||||
if (options.hasText instanceof Pattern) {
|
||||
Pattern pattern = (Pattern) options.hasText;
|
||||
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 = convertType(options, ElementHandleOptions.class);
|
||||
ElementHandleOptions handleOptions = convertViaJson(options, ElementHandleOptions.class);
|
||||
// TODO: support deadline based timeout
|
||||
// Double timeout = null;
|
||||
// if (handleOptions != null) {
|
||||
@@ -89,7 +63,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new CheckOptions();
|
||||
}
|
||||
frame.check(selector, convertType(options, Frame.CheckOptions.class).setStrict(true));
|
||||
frame.check(selector, convertViaJson(options, Frame.CheckOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,12 +71,12 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new ClickOptions();
|
||||
}
|
||||
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
|
||||
frame.click(selector, convertViaJson(options, Frame.ClickOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return frame.queryCount(selector);
|
||||
return ((Number) evaluateAll("ee => ee.length")).intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,7 +84,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new DblclickOptions();
|
||||
}
|
||||
frame.dblclick(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true));
|
||||
frame.dblclick(selector, convertViaJson(options, Frame.DblclickOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,17 +92,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new DispatchEventOptions();
|
||||
}
|
||||
frame.dispatchEvent(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragTo(Locator target, DragToOptions options) {
|
||||
if (options == null) {
|
||||
options = new DragToOptions();
|
||||
}
|
||||
Frame.DragAndDropOptions frameOptions = convertType(options, Frame.DragAndDropOptions.class);
|
||||
frameOptions.setStrict(true);
|
||||
frame.dragAndDrop(selector, ((LocatorImpl) target).selector, frameOptions);
|
||||
frame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +100,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new ElementHandleOptions();
|
||||
}
|
||||
Frame.WaitForSelectorOptions frameOptions = convertType(options, Frame.WaitForSelectorOptions.class);
|
||||
Frame.WaitForSelectorOptions frameOptions = convertViaJson(options, Frame.WaitForSelectorOptions.class);
|
||||
frameOptions.setStrict(true);
|
||||
frameOptions.setState(WaitForSelectorState.ATTACHED);
|
||||
return frame.waitForSelector(selector, frameOptions);
|
||||
@@ -167,12 +131,12 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new FillOptions();
|
||||
}
|
||||
frame.fill(selector, value, convertType(options, Frame.FillOptions.class).setStrict(true));
|
||||
frame.fill(selector, value, convertViaJson(options, Frame.FillOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator first() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=0", null);
|
||||
return new LocatorImpl(frame, selector + " >> nth=0");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,12 +144,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new FocusOptions();
|
||||
}
|
||||
frame.focus(selector, convertType(options, Frame.FocusOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocatorImpl frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(frame, this.selector + " >> " + selector);
|
||||
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,7 +152,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new GetAttributeOptions();
|
||||
}
|
||||
return frame.getAttribute(selector, name, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
|
||||
return frame.getAttribute(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,7 +160,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new HoverOptions();
|
||||
}
|
||||
frame.hover(selector, convertType(options, Frame.HoverOptions.class).setStrict(true));
|
||||
frame.hover(selector, convertViaJson(options, Frame.HoverOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -209,7 +168,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new InnerHTMLOptions();
|
||||
}
|
||||
return frame.innerHTML(selector, convertType(options, Frame.InnerHTMLOptions.class).setStrict(true));
|
||||
return frame.innerHTML(selector, convertViaJson(options, Frame.InnerHTMLOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,7 +176,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new InnerTextOptions();
|
||||
}
|
||||
return frame.innerText(selector, convertType(options, Frame.InnerTextOptions.class).setStrict(true));
|
||||
return frame.innerText(selector, convertViaJson(options, Frame.InnerTextOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,7 +184,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new InputValueOptions();
|
||||
}
|
||||
return frame.inputValue(selector, convertType(options, Frame.InputValueOptions.class).setStrict(true));
|
||||
return frame.inputValue(selector, convertViaJson(options, Frame.InputValueOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,7 +192,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsCheckedOptions();
|
||||
}
|
||||
return frame.isChecked(selector, convertType(options, Frame.IsCheckedOptions.class).setStrict(true));
|
||||
return frame.isChecked(selector, convertViaJson(options, Frame.IsCheckedOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -241,7 +200,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsDisabledOptions();
|
||||
}
|
||||
return frame.isDisabled(selector, convertType(options, Frame.IsDisabledOptions.class).setStrict(true));
|
||||
return frame.isDisabled(selector, convertViaJson(options, Frame.IsDisabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -249,7 +208,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsEditableOptions();
|
||||
}
|
||||
return frame.isEditable(selector, convertType(options, Frame.IsEditableOptions.class).setStrict(true));
|
||||
return frame.isEditable(selector, convertViaJson(options, Frame.IsEditableOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -257,7 +216,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsEnabledOptions();
|
||||
}
|
||||
return frame.isEnabled(selector, convertType(options, Frame.IsEnabledOptions.class).setStrict(true));
|
||||
return frame.isEnabled(selector, convertViaJson(options, Frame.IsEnabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -265,7 +224,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsHiddenOptions();
|
||||
}
|
||||
return frame.isHidden(selector, convertType(options, Frame.IsHiddenOptions.class).setStrict(true));
|
||||
return frame.isHidden(selector, convertViaJson(options, Frame.IsHiddenOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,27 +232,22 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
}
|
||||
return frame.isVisible(selector, convertType(options, Frame.IsVisibleOptions.class).setStrict(true));
|
||||
return frame.isVisible(selector, convertViaJson(options, Frame.IsVisibleOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator last() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=-1", null);
|
||||
return new LocatorImpl(frame, selector + " >> nth=-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(frame, this.selector + " >> " + selector, options);
|
||||
public Locator locator(String selector) {
|
||||
return new LocatorImpl(frame, this.selector + " >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator nth(int index) {
|
||||
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page page() {
|
||||
return frame.page();
|
||||
return new LocatorImpl(frame, selector + " >> nth=" + index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -301,12 +255,12 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new PressOptions();
|
||||
}
|
||||
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
|
||||
frame.press(selector, key, convertViaJson(options, Frame.PressOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] screenshot(ScreenshotOptions options) {
|
||||
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
|
||||
return withElement((h, o) -> h.screenshot(o), convertViaJson(options, ElementHandle.ScreenshotOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -314,7 +268,7 @@ class LocatorImpl implements Locator {
|
||||
withElement((h, o) -> {
|
||||
h.scrollIntoViewIfNeeded(o);
|
||||
return null;
|
||||
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
|
||||
}, convertViaJson(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -322,7 +276,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,7 +284,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -338,7 +292,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,7 +300,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -354,7 +308,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -362,7 +316,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,7 +324,7 @@ class LocatorImpl implements Locator {
|
||||
withElement((h, o) -> {
|
||||
h.selectText(o);
|
||||
return null;
|
||||
}, convertType(options, ElementHandle.SelectTextOptions.class));
|
||||
}, convertViaJson(options, ElementHandle.SelectTextOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -378,7 +332,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SetCheckedOptions();
|
||||
}
|
||||
frame.setChecked(selector, checked, convertType(options, Frame.SetCheckedOptions.class).setStrict(true));
|
||||
frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -386,7 +340,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -394,7 +348,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -402,7 +356,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -410,7 +364,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -418,7 +372,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new TapOptions();
|
||||
}
|
||||
frame.tap(selector, convertType(options, Frame.TapOptions.class).setStrict(true));
|
||||
frame.tap(selector, convertViaJson(options, Frame.TapOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -426,7 +380,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new TextContentOptions();
|
||||
}
|
||||
return frame.textContent(selector, convertType(options, Frame.TextContentOptions.class).setStrict(true));
|
||||
return frame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -434,7 +388,7 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new TypeOptions();
|
||||
}
|
||||
frame.type(selector, text, convertType(options, Frame.TypeOptions.class).setStrict(true));
|
||||
frame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -442,39 +396,11 @@ class LocatorImpl implements Locator {
|
||||
if (options == null) {
|
||||
options = new UncheckOptions();
|
||||
}
|
||||
frame.uncheck(selector, convertType(options, Frame.UncheckOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitFor(WaitForOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForOptions();
|
||||
}
|
||||
waitForImpl(options);
|
||||
}
|
||||
|
||||
private void waitForImpl(WaitForOptions options) {
|
||||
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
|
||||
frame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class).setStrict(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.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
class MouseImpl implements Mouse {
|
||||
private final ChannelOwner page;
|
||||
@@ -54,7 +54,7 @@ class MouseImpl implements Mouse {
|
||||
if (options == null) {
|
||||
clickOptions = new ClickOptions();
|
||||
} else {
|
||||
clickOptions = convertType(options, ClickOptions.class);
|
||||
clickOptions = convertViaJson(options, ClickOptions.class);
|
||||
}
|
||||
clickOptions.clickCount = 2;
|
||||
click(x, y, clickOptions);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.assertions.PageAssertions;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
public class PageAssertionsImpl extends AssertionsBase implements PageAssertions {
|
||||
private final PageImpl actualPage;
|
||||
|
||||
public PageAssertionsImpl(Page page) {
|
||||
this(page, false);
|
||||
}
|
||||
|
||||
private PageAssertionsImpl(Page page, boolean isNot) {
|
||||
super((LocatorImpl) page.locator(":root"), isNot);
|
||||
this.actualPage = (PageImpl) page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasTitle(String title, HasTitleOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = title;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasTitle(Pattern pattern, HasTitleOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasURL(String url, HasURLOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
if (actualPage.context().baseUrl != null) {
|
||||
url = resolveUrl(actualPage.context().baseUrl, url);
|
||||
}
|
||||
expected.string = url;
|
||||
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasURL(Pattern pattern, HasURLOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAssertions not() {
|
||||
return new PageAssertionsImpl(actualPage, !isNot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,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,9 +170,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
try {
|
||||
bindingCall.call(binding);
|
||||
} catch (RuntimeException e) {
|
||||
if (!isSafeCloseError(e.getMessage())) {
|
||||
logWithTimestamp(e.getMessage());
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else if ("load".equals(event)) {
|
||||
@@ -200,10 +198,11 @@ 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) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
browserContext.handleRoute(route);
|
||||
if (!handled) {
|
||||
handled = browserContext.routes.handle(route);
|
||||
}
|
||||
if (!handled) {
|
||||
route.resume();
|
||||
}
|
||||
} else if ("video".equals(event)) {
|
||||
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
|
||||
@@ -558,7 +557,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
|
||||
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
|
||||
selector, convertType(options, Frame.QuerySelectorOptions.class)));
|
||||
selector, convertViaJson(options, Frame.QuerySelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -569,7 +568,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
|
||||
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
|
||||
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class)));
|
||||
selector, pageFunction, arg, convertViaJson(options, Frame.EvalOnSelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -603,13 +602,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public ElementHandle addScriptTag(AddScriptTagOptions options) {
|
||||
return withLogging("Page.addScriptTag",
|
||||
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
|
||||
() -> mainFrame.addScriptTagImpl(convertViaJson(options, Frame.AddScriptTagOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle addStyleTag(AddStyleTagOptions options) {
|
||||
return withLogging("Page.addStyleTag",
|
||||
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
|
||||
() -> mainFrame.addStyleTagImpl(convertViaJson(options, Frame.AddStyleTagOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -620,13 +619,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void check(String selector, CheckOptions options) {
|
||||
withLogging("Page.check",
|
||||
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
|
||||
() -> mainFrame.checkImpl(selector, convertViaJson(options, Frame.CheckOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(String selector, ClickOptions options) {
|
||||
withLogging("Page.click",
|
||||
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
|
||||
() -> mainFrame.clickImpl(selector, convertViaJson(options, Frame.ClickOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -642,13 +641,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void dblclick(String selector, DblclickOptions options) {
|
||||
withLogging("Page.dblclick",
|
||||
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
|
||||
() -> mainFrame.dblclickImpl(selector, convertViaJson(options, Frame.DblclickOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
|
||||
withLogging("Page.dispatchEvent",
|
||||
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
|
||||
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -705,13 +704,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void fill(String selector, String value, FillOptions options) {
|
||||
withLogging("Page.fill",
|
||||
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
|
||||
() -> mainFrame.fillImpl(selector, value, convertViaJson(options, Frame.FillOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus(String selector, FocusOptions options) {
|
||||
withLogging("Page.focus",
|
||||
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
|
||||
() -> mainFrame.focusImpl(selector, convertViaJson(options, Frame.FocusOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -739,11 +738,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return frameFor(new UrlMatcher(predicate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator frameLocator(String selector) {
|
||||
return mainFrame.frameLocator(selector);
|
||||
}
|
||||
|
||||
private Frame frameFor(UrlMatcher matcher) {
|
||||
for (Frame frame : frames) {
|
||||
if (matcher.test(frame.url())) {
|
||||
@@ -761,7 +755,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public String getAttribute(String selector, String name, GetAttributeOptions options) {
|
||||
return withLogging("Page.getAttribute",
|
||||
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
|
||||
() -> mainFrame.getAttributeImpl(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -800,41 +794,44 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public ResponseImpl navigate(String url, NavigateOptions options) {
|
||||
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
|
||||
return withLogging("Page.navigate", () ->
|
||||
mainFrame.navigateImpl(url, convertViaJson(options, Frame.NavigateOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hover(String selector, HoverOptions options) {
|
||||
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class)));
|
||||
withLogging("Page.hover", () ->
|
||||
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
|
||||
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class)));
|
||||
withLogging("Page.dragAndDrop", () ->
|
||||
mainFrame.dragAndDropImpl(source, target, convertViaJson(options, Frame.DragAndDropOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(String selector, InnerHTMLOptions options) {
|
||||
return withLogging("Page.innerHTML",
|
||||
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
|
||||
() -> mainFrame.innerHTMLImpl(selector, convertViaJson(options, Frame.InnerHTMLOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerText(String selector, InnerTextOptions options) {
|
||||
return withLogging("Page.innerText",
|
||||
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
|
||||
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(String selector, InputValueOptions options) {
|
||||
return withLogging("Page.inputValue",
|
||||
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
|
||||
() -> mainFrame.inputValueImpl(selector, convertViaJson(options, Frame.InputValueOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(String selector, IsCheckedOptions options) {
|
||||
return withLogging("Page.isChecked",
|
||||
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
|
||||
() -> mainFrame.isCheckedImpl(selector, convertViaJson(options, Frame.IsCheckedOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -845,31 +842,31 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public boolean isDisabled(String selector, IsDisabledOptions options) {
|
||||
return withLogging("Page.isDisabled",
|
||||
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
|
||||
() -> mainFrame.isDisabledImpl(selector, convertViaJson(options, Frame.IsDisabledOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(String selector, IsEditableOptions options) {
|
||||
return withLogging("Page.isEditable",
|
||||
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
|
||||
() -> mainFrame.isEditableImpl(selector, convertViaJson(options, Frame.IsEditableOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(String selector, IsEnabledOptions options) {
|
||||
return withLogging("Page.isEnabled",
|
||||
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
|
||||
() -> mainFrame.isEnabledImpl(selector, convertViaJson(options, Frame.IsEnabledOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden(String selector, IsHiddenOptions options) {
|
||||
return withLogging("Page.isHidden",
|
||||
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
|
||||
() -> mainFrame.isHiddenImpl(selector, convertViaJson(options, Frame.IsHiddenOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible(String selector, IsVisibleOptions options) {
|
||||
return withLogging("Page.isVisible",
|
||||
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
|
||||
() -> mainFrame.isVisibleImpl(selector, convertViaJson(options, Frame.IsVisibleOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -878,8 +875,8 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class));
|
||||
public Locator locator(String selector) {
|
||||
return mainFrame.locator(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -932,7 +929,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void press(String selector, String key, PressOptions options) {
|
||||
withLogging("Page.press",
|
||||
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
|
||||
() -> mainFrame.pressImpl(selector, key, convertViaJson(options, Frame.PressOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -940,11 +937,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return withLogging("Page.reload", () -> reloadImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequestContextImpl request() {
|
||||
return browserContext.request();
|
||||
}
|
||||
|
||||
private Response reloadImpl(ReloadOptions options) {
|
||||
if (options == null) {
|
||||
options = new ReloadOptions();
|
||||
@@ -1048,25 +1040,25 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
|
||||
return withLogging("Page.selectOption",
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
|
||||
return withLogging("Page.selectOption",
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
|
||||
withLogging("Page.setChecked",
|
||||
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
|
||||
() -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Page.setContent",
|
||||
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
|
||||
() -> mainFrame.setContentImpl(html, convertViaJson(options, Frame.SetContentOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1113,7 +1105,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
withLogging("Page.setInputFiles",
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1124,7 +1116,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Page.setInputFiles",
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1140,13 +1132,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void tap(String selector, TapOptions options) {
|
||||
withLogging("Page.tap",
|
||||
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
|
||||
() -> mainFrame.tapImpl(selector, convertViaJson(options, Frame.TapOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textContent(String selector, TextContentOptions options) {
|
||||
return withLogging("Page.textContent",
|
||||
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
|
||||
() -> mainFrame.textContentImpl(selector, convertViaJson(options, Frame.TextContentOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1162,13 +1154,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void type(String selector, String text, TypeOptions options) {
|
||||
withLogging("Page.type",
|
||||
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
|
||||
() -> mainFrame.typeImpl(selector, text, convertViaJson(options, Frame.TypeOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncheck(String selector, UncheckOptions options) {
|
||||
withLogging("Page.uncheck",
|
||||
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
|
||||
() -> mainFrame.uncheckImpl(selector, convertViaJson(options, Frame.UncheckOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1189,18 +1181,14 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
withLogging("Page.unroute", () -> {
|
||||
routes.remove(matcher, handler);
|
||||
maybeDisableNetworkInterception();
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeDisableNetworkInterception() {
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return mainFrame.url();
|
||||
@@ -1241,13 +1229,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
|
||||
return withLogging("Page.waitForFunction",
|
||||
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
|
||||
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertViaJson(options, Frame.WaitForFunctionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withWaitLogging("Page.waitForLoadState", () -> {
|
||||
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class));
|
||||
mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -1381,7 +1369,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
|
||||
return withLogging("Page.waitForSelector",
|
||||
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
|
||||
() -> mainFrame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1405,7 +1393,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertType(options, Frame.WaitForURLOptions.class)));
|
||||
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.*;
|
||||
@@ -25,10 +24,8 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
public class PipeTransport implements Transport {
|
||||
private final BlockingQueue<JsonObject> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
|
||||
|
||||
private final ReaderThread readerThread;
|
||||
@@ -45,27 +42,24 @@ public class PipeTransport implements Transport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(JsonObject message) {
|
||||
public void send(String message) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
// We could serialize the message on the IO thread but there is no guarantee
|
||||
// that the message object won't be modified on this thread after it's added
|
||||
// to the queue.
|
||||
outgoing.put(gson().toJson(message));
|
||||
outgoing.put(message);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject poll(Duration timeout) {
|
||||
public String poll(Duration timeout) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
JsonObject message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
if (message == null && readerThread.exception != null) {
|
||||
try {
|
||||
close();
|
||||
@@ -97,7 +91,7 @@ public class PipeTransport implements Transport {
|
||||
|
||||
class ReaderThread extends Thread {
|
||||
private final DataInputStream in;
|
||||
private final BlockingQueue<JsonObject> queue;
|
||||
private final BlockingQueue<String> queue;
|
||||
volatile boolean isClosing;
|
||||
volatile Exception exception;
|
||||
|
||||
@@ -113,7 +107,7 @@ class ReaderThread extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
ReaderThread(DataInputStream in, BlockingQueue<JsonObject> queue) {
|
||||
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
|
||||
this.in = in;
|
||||
this.queue = queue;
|
||||
}
|
||||
@@ -122,8 +116,7 @@ class ReaderThread extends Thread {
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
JsonObject message = gson().fromJson(readMessage(), JsonObject.class);
|
||||
queue.put(message);
|
||||
queue.put(readMessage());
|
||||
} catch (IOException e) {
|
||||
if (!isInterrupted() && !isClosing) {
|
||||
exception = e;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequest;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Selectors;
|
||||
@@ -37,11 +36,10 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
if (options != null && options.env != null) {
|
||||
env = options.env;
|
||||
}
|
||||
Path driver = Driver.ensureDriverInstalled(env, true);
|
||||
Path driver = Driver.ensureDriverInstalled(env);
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.environment().putAll(env);
|
||||
Driver.setRequiredEnvironmentVariables(pb);
|
||||
Process p = pb.start();
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
@@ -57,9 +55,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private final BrowserTypeImpl firefox;
|
||||
private final BrowserTypeImpl webkit;
|
||||
private final SelectorsImpl selectors;
|
||||
private final APIRequestImpl apiRequest;
|
||||
private final LocalUtils localUtils;
|
||||
private SharedSelectors sharedSelectors;
|
||||
private SharedSelectors sharedSelectors;;
|
||||
|
||||
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -68,17 +64,12 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
|
||||
|
||||
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
apiRequest = new APIRequestImpl(this);
|
||||
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
|
||||
chromium.localUtils = localUtils;
|
||||
firefox.localUtils = localUtils;
|
||||
webkit.localUtils = localUtils;
|
||||
}
|
||||
|
||||
void initSharedSelectors(PlaywrightImpl parent) {
|
||||
assert sharedSelectors == null;
|
||||
if (parent == null) {
|
||||
sharedSelectors = new SharedSelectors();
|
||||
sharedSelectors = new SharedSelectors();;
|
||||
} else {
|
||||
sharedSelectors = parent.sharedSelectors;
|
||||
}
|
||||
@@ -99,11 +90,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
return firefox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequest request() {
|
||||
return apiRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserTypeImpl webkit() {
|
||||
return webkit;
|
||||
|
||||
@@ -18,12 +18,18 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import java.util.List;
|
||||
class Binary {
|
||||
}
|
||||
|
||||
class Channel {
|
||||
String guid;
|
||||
}
|
||||
|
||||
class Metadata{
|
||||
String stack;
|
||||
}
|
||||
|
||||
|
||||
class SerializedValue{
|
||||
Number n;
|
||||
Boolean b;
|
||||
@@ -45,11 +51,45 @@ 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;
|
||||
@@ -79,28 +119,3 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
import com.microsoft.playwright.options.RequestOptions;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestOptionsImpl implements RequestOptions {
|
||||
Map<String, Object> params;
|
||||
String method;
|
||||
Map<String, String> headers;
|
||||
Object data;
|
||||
FormDataImpl form;
|
||||
FormDataImpl multipart;
|
||||
Boolean failOnStatusCode;
|
||||
Boolean ignoreHTTPSErrors;
|
||||
Double timeout;
|
||||
|
||||
@Override
|
||||
public RequestOptions setHeader(String name, String value) {
|
||||
if (headers == null) {
|
||||
headers = new LinkedHashMap<>();
|
||||
}
|
||||
headers.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(String data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setForm(FormData form) {
|
||||
this.form = (FormDataImpl) form;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMultipart(FormData form) {
|
||||
this.multipart = (FormDataImpl) form;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, String value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, boolean value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, int value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
private RequestOptions setQueryParamImpl(String name, Object value) {
|
||||
if (params == null) {
|
||||
params = new LinkedHashMap<>();
|
||||
}
|
||||
params.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setFailOnStatusCode(boolean failOnStatusCode) {
|
||||
this.failOnStatusCode = failOnStatusCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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;
|
||||
@@ -28,25 +29,21 @@ 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);
|
||||
sendMessageAsync("abort", params);
|
||||
sendMessage("abort", params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(ResumeOptions options) {
|
||||
startHandling();
|
||||
withLogging("Route.resume", () -> resumeImpl(options));
|
||||
}
|
||||
|
||||
@@ -76,12 +73,11 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
sendMessageAsync("continue", params);
|
||||
sendMessage("continue", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fulfill(FulfillOptions options) {
|
||||
startHandling();
|
||||
withLogging("Route.fulfill", () -> fulfillImpl(options));
|
||||
}
|
||||
|
||||
@@ -90,30 +86,16 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
options = new FulfillOptions();
|
||||
}
|
||||
|
||||
Integer status = options.status;
|
||||
Map<String, String> headersOption = options.headers;
|
||||
String fetchResponseUid = null;
|
||||
|
||||
if (options.response != null) {
|
||||
if (status == null) {
|
||||
status = options.response.status();
|
||||
}
|
||||
if (headersOption == null) {
|
||||
headersOption = options.response.headers();
|
||||
}
|
||||
}
|
||||
if (status == null) {
|
||||
status = 200;
|
||||
}
|
||||
String body = null;
|
||||
int status = options.status == null ? 200 : options.status;
|
||||
String body = "";
|
||||
boolean isBase64 = false;
|
||||
int length = 0;
|
||||
if (options.path != null) {
|
||||
try {
|
||||
byte[] buffer = Files.readAllBytes(options.path);
|
||||
body = Base64.getEncoder().encodeToString(buffer);
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
byte[] buffer = Files.readAllBytes(options.path);
|
||||
body = Base64.getEncoder().encodeToString(buffer);
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file: " + options.path, e);
|
||||
}
|
||||
@@ -125,22 +107,11 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
body = Base64.getEncoder().encodeToString(options.bodyBytes);
|
||||
isBase64 = true;
|
||||
length = options.bodyBytes.length;
|
||||
} else if (options.response != null) {
|
||||
APIResponseImpl response = (APIResponseImpl) options.response;
|
||||
if (response.context.connection == connection) {
|
||||
fetchResponseUid = response.fetchUid();
|
||||
} else {
|
||||
byte[] bodyBytes = response.body();
|
||||
body = Base64.getEncoder().encodeToString(bodyBytes);
|
||||
isBase64 = true;
|
||||
length = bodyBytes.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
if (headersOption != null) {
|
||||
for (Map.Entry<String, String> h : headersOption.entrySet()) {
|
||||
if (options.headers != null) {
|
||||
for (Map.Entry<String, String> h : options.headers.entrySet()) {
|
||||
headers.put(h.getKey().toLowerCase(), h.getValue());
|
||||
}
|
||||
}
|
||||
@@ -157,21 +128,11 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
params.add("headers", Serialization.toProtocol(headers));
|
||||
params.addProperty("isBase64", isBase64);
|
||||
params.addProperty("body", body);
|
||||
if (fetchResponseUid != null) {
|
||||
params.addProperty("fetchResponseUid", fetchResponseUid);
|
||||
}
|
||||
sendMessageAsync("fulfill", params);
|
||||
sendMessage("fulfill", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestImpl request() {
|
||||
public Request request() {
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
|
||||
}
|
||||
|
||||
private void startHandling() {
|
||||
if (handled) {
|
||||
throw new PlaywrightException("Route is already handled!");
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,10 +50,6 @@ class Router {
|
||||
handler.accept(route);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isDone() {
|
||||
return times != null && times <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
void add(UrlMatcher matcher, Consumer<Route> handler, Integer times) {
|
||||
@@ -73,9 +69,6 @@ class Router {
|
||||
boolean handle(Route route) {
|
||||
for (RouteInfo info : routes) {
|
||||
if (info.handle(route)) {
|
||||
if (info.isDone()) {
|
||||
routes.remove(info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,26 +33,29 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
class Serialization {
|
||||
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>())
|
||||
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
|
||||
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
|
||||
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
|
||||
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.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();
|
||||
private static Gson gson;
|
||||
|
||||
static Gson gson() {
|
||||
if (gson == null) {
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
|
||||
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
|
||||
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
|
||||
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
|
||||
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
|
||||
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.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();
|
||||
}
|
||||
return gson;
|
||||
}
|
||||
|
||||
@@ -212,19 +215,15 @@ class Serialization {
|
||||
static JsonArray toJsonArray(FilePayload[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (FilePayload p : files) {
|
||||
jsonFiles.add(toProtocol(p));
|
||||
JsonObject jsonFile = new JsonObject();
|
||||
jsonFile.addProperty("name", p.name);
|
||||
jsonFile.addProperty("mimeType", p.mimeType);
|
||||
jsonFile.addProperty("buffer", Base64.getEncoder().encodeToString(p.buffer));
|
||||
jsonFiles.add(jsonFile);
|
||||
}
|
||||
return jsonFiles;
|
||||
}
|
||||
|
||||
static JsonObject toProtocol(FilePayload p) {
|
||||
JsonObject jsonFile = new JsonObject();
|
||||
jsonFile.addProperty("name", p.name);
|
||||
jsonFile.addProperty("mimeType", p.mimeType);
|
||||
jsonFile.addProperty("buffer", Base64.getEncoder().encodeToString(p.buffer));
|
||||
return jsonFile;
|
||||
}
|
||||
|
||||
static JsonArray toProtocol(ElementHandle[] handles) {
|
||||
JsonArray jsonElements = new JsonArray();
|
||||
for (ElementHandle handle : handles) {
|
||||
@@ -236,15 +235,11 @@ 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, ?> e : map.entrySet()) {
|
||||
for (Map.Entry<String, String> e : map.entrySet()) {
|
||||
JsonObject item = new JsonObject();
|
||||
item.addProperty("name", e.getKey());
|
||||
item.add("value", gson().toJsonTree(e.getValue()));
|
||||
item.addProperty("value", e.getValue());
|
||||
array.add(item);
|
||||
}
|
||||
return array;
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class StackTraceCollector {
|
||||
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,102 +16,70 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Tracing;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TracingImpl extends ChannelOwner implements Tracing {
|
||||
LocalUtils localUtils;
|
||||
boolean isRemote;
|
||||
private boolean includeSources;
|
||||
class TracingImpl implements Tracing {
|
||||
private final BrowserContextImpl context;
|
||||
|
||||
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
TracingImpl(BrowserContextImpl context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void stopChunkImpl(Path path) {
|
||||
JsonObject params = new JsonObject();
|
||||
String mode = "doNotSave";
|
||||
if (path != null) {
|
||||
if (isRemote) {
|
||||
mode = "compressTrace";
|
||||
} else {
|
||||
mode = "compressTraceAndSources";
|
||||
}
|
||||
}
|
||||
params.addProperty("mode", mode);
|
||||
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
|
||||
params.addProperty("save", path != null);
|
||||
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
|
||||
if (!json.has("artifact")) {
|
||||
return;
|
||||
}
|
||||
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
// In case of CDP connection browser is null but since the connection is established by
|
||||
// the driver it is safe to consider the artifact local.
|
||||
if (isRemote) {
|
||||
if (context.browser() != null && context.browser().isRemote) {
|
||||
artifact.isRemote = true;
|
||||
}
|
||||
artifact.saveAs(path);
|
||||
artifact.delete();
|
||||
|
||||
// Add local sources to the remote trace if necessary.
|
||||
if (isRemote && json.has("sourceEntries")) {
|
||||
JsonArray entries = json.getAsJsonArray("sourceEntries");
|
||||
localUtils.zip(path, entries);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(StartOptions options) {
|
||||
withLogging("Tracing.start", () -> startImpl(options));
|
||||
context.withLogging("Tracing.start", () -> startImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startChunk(StartChunkOptions options) {
|
||||
withLogging("Tracing.startChunk", () -> {
|
||||
startChunkImpl(options);
|
||||
public void startChunk() {
|
||||
context.withLogging("Tracing.startChunk", () -> {
|
||||
context.sendMessage("tracingStartChunk");
|
||||
});
|
||||
}
|
||||
|
||||
private void startChunkImpl(StartChunkOptions options) {
|
||||
if (options == null) {
|
||||
options = new StartChunkOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
sendMessage("tracingStartChunk", params);
|
||||
}
|
||||
|
||||
private void startImpl(StartOptions options) {
|
||||
if (options == null) {
|
||||
options = new StartOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
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");
|
||||
context.sendMessage("tracingStart", params);
|
||||
context.sendMessage("tracingStartChunk");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(StopOptions options) {
|
||||
withLogging("Tracing.stop", () -> {
|
||||
context.withLogging("Tracing.stop", () -> {
|
||||
stopChunkImpl(options == null ? null : options.path);
|
||||
sendMessage("tracingStop");
|
||||
context.sendMessage("tracingStop");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopChunk(StopChunkOptions options) {
|
||||
withLogging("Tracing.stopChunk", () -> {
|
||||
context.withLogging("Tracing.stopChunk", () -> {
|
||||
stopChunkImpl(options == null ? null : options.path);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
public interface Transport {
|
||||
void send(JsonObject message);
|
||||
JsonObject poll(Duration timeout);
|
||||
void send(String message);
|
||||
String poll(Duration timeout);
|
||||
void close() throws IOException;
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TransportLogger implements Transport {
|
||||
private final Transport transport;
|
||||
|
||||
TransportLogger(Transport transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(JsonObject message) {
|
||||
String messageString = gson().toJson(message);
|
||||
logWithTimestamp("SEND ► " + messageString);
|
||||
transport.send(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject poll(Duration timeout) {
|
||||
JsonObject message = transport.poll(timeout);
|
||||
if (message != null) {
|
||||
String messageString = gson().toJson(message);
|
||||
logWithTimestamp("◀ RECV " + messageString);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
transport.close();
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class UrlMatcher {
|
||||
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
|
||||
}
|
||||
|
||||
static String resolveUrl(URL baseUrl, String spec) {
|
||||
private static String resolveUrl(URL baseUrl, String spec) {
|
||||
if (baseUrl == null) {
|
||||
return spec;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
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;
|
||||
@@ -23,54 +24,32 @@ import com.microsoft.playwright.options.HttpHeader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Utils {
|
||||
static <F, T> T convertType(F f, Class<T> t) {
|
||||
if (f == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO: generate converter.
|
||||
static <F, T> T convertViaJson(F f, Class<T> t) {
|
||||
Gson gson = new GsonBuilder()
|
||||
// Necessary to avoid access to private fields/classes,
|
||||
// see https://github.com/microsoft/playwright-java/issues/423
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.create();
|
||||
String json = gson.toJson(f);
|
||||
return gson.fromJson(json, t);
|
||||
}
|
||||
|
||||
// Make sure shallow copy is sufficient
|
||||
if (!t.getSuperclass().equals(Object.class) && !t.getSuperclass().equals(Enum.class)) {
|
||||
throw new PlaywrightException("Cannot convert to " + t.getCanonicalName() + " that has superclass " + t.getSuperclass().getCanonicalName());
|
||||
}
|
||||
if (!f.getClass().getSuperclass().equals(t.getSuperclass())) {
|
||||
throw new PlaywrightException("Cannot convert from " + t.getCanonicalName() + " that has superclass " + t.getSuperclass().getCanonicalName());
|
||||
}
|
||||
|
||||
if (f instanceof Enum) {
|
||||
return (T) Enum.valueOf((Class) t, ((Enum) f).name());
|
||||
}
|
||||
|
||||
try {
|
||||
T result = t.getDeclaredConstructor().newInstance();
|
||||
for (Field toField : t.getDeclaredFields()) {
|
||||
// Skip fields added by test coverage tools, see https://github.com/microsoft/playwright-java/issues/802
|
||||
if (toField.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isStatic(toField.getModifiers())) {
|
||||
throw new RuntimeException("Unexpected field modifiers: " + t.getCanonicalName() + "." + toField.getName() + ", modifiers: " + toField.getModifiers());
|
||||
}
|
||||
try {
|
||||
Field fromField = f.getClass().getDeclaredField(toField.getName());
|
||||
Object value = fromField.get(f);
|
||||
if (value != null) {
|
||||
toField.set(result, value);
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
continue;
|
||||
}
|
||||
private static class OptionalSerializer implements JsonSerializer<Optional> {
|
||||
@Override
|
||||
public JsonElement serialize(Optional src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject result = new JsonObject();
|
||||
if (src.isPresent()) {
|
||||
result.add("value", context.serialize(src.get()));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new PlaywrightException("Internal error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +62,7 @@ class Utils {
|
||||
for (int i = 0; i < glob.length(); ++i) {
|
||||
char c = glob.charAt(i);
|
||||
if (escapeGlobChars.contains(c)) {
|
||||
tokens.append("\\").append(c);
|
||||
tokens.append("\\" + c);
|
||||
continue;
|
||||
}
|
||||
if (c == '*') {
|
||||
@@ -121,7 +100,7 @@ class Utils {
|
||||
tokens.append('|');
|
||||
break;
|
||||
}
|
||||
tokens.append("\\").append(c);
|
||||
tokens.append("\\" + c);
|
||||
break;
|
||||
default:
|
||||
tokens.append(c);
|
||||
@@ -147,21 +126,17 @@ class Utils {
|
||||
static FilePayload[] toFilePayloads(Path[] files) {
|
||||
List<FilePayload> payloads = new ArrayList<>();
|
||||
for (Path file : files) {
|
||||
payloads.add(toFilePayload(file));
|
||||
byte[] buffer;
|
||||
try {
|
||||
buffer = Files.readAllBytes(file);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file", e);
|
||||
}
|
||||
payloads.add(new FilePayload(file.getFileName().toString(), mimeType(file), buffer));
|
||||
}
|
||||
return payloads.toArray(new FilePayload[0]);
|
||||
}
|
||||
|
||||
static FilePayload toFilePayload(Path file) {
|
||||
byte[] buffer;
|
||||
try {
|
||||
buffer = Files.readAllBytes(file);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file", e);
|
||||
}
|
||||
return new FilePayload(file.getFileName().toString(), null, buffer);
|
||||
}
|
||||
|
||||
static void mkParentDirs(Path file) {
|
||||
Path dir = file.getParent();
|
||||
if (dir != null) {
|
||||
@@ -169,7 +144,7 @@ class Utils {
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to create parent directory: " + dir, e);
|
||||
throw new PlaywrightException("Failed to create parent directory: " + dir.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +177,7 @@ class Utils {
|
||||
}
|
||||
|
||||
static boolean isSafeCloseError(String error) {
|
||||
return error.contains("Browser has been closed") || error.contains("Target page, context or browser has been closed");
|
||||
return error.endsWith("Browser has been closed") || error.endsWith("Target page, context or browser has been closed");
|
||||
}
|
||||
|
||||
static String createGuid() {
|
||||
@@ -221,24 +196,4 @@ 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,4 +22,7 @@ interface Waitable<T> {
|
||||
boolean isDone();
|
||||
T get();
|
||||
void dispose();
|
||||
default <U> Waitable<U> apply(Function<T, U> transform) {
|
||||
return new WaitableAdapter<T, U>(this, transform);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -16,20 +16,28 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.nio.file.Path;
|
||||
class WaitableAdapter<F, T> implements Waitable<T> {
|
||||
private final Waitable<F> waitable;
|
||||
private final Function<F, T> transformation;
|
||||
|
||||
class LocalUtils extends ChannelOwner {
|
||||
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
WaitableAdapter(Waitable<F> waitable, Function<F, T> transformation) {
|
||||
this.waitable = waitable;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return waitable.isDone();
|
||||
}
|
||||
|
||||
void zip(Path zipFile, JsonArray entries) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("zipFile", zipFile.toString());
|
||||
params.add("entries", entries);
|
||||
sendMessage("zip", params);
|
||||
@Override
|
||||
public T get() {
|
||||
return transformation.apply(waitable.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
waitable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -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.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
class WebSocketTransport implements Transport {
|
||||
private final BlockingQueue<String> incoming = new LinkedBlockingQueue<>();
|
||||
private final ClientConnection clientConnection;
|
||||
private final Duration slowMo;
|
||||
private boolean isClosed;
|
||||
private volatile Exception lastError;
|
||||
ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
|
||||
private enum EventType { CLOSE }
|
||||
|
||||
private class ClientConnection extends WebSocketClient {
|
||||
ClientConnection(URI serverUri) {
|
||||
super(serverUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
incoming.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
lastError = ex;
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketTransport(URI uri, Map<String, String> headers, Duration timeout, Duration slowMo) {
|
||||
clientConnection = new ClientConnection(uri);
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
clientConnection.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
try {
|
||||
if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
|
||||
throw new PlaywrightException("Failed to connect", lastError);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
}
|
||||
this.slowMo = slowMo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
checkIfClosed();
|
||||
clientConnection.send(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String poll(Duration timeout) {
|
||||
checkIfClosed();
|
||||
try {
|
||||
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
if (slowMo != null && message != null) {
|
||||
Thread.sleep(slowMo.toMillis());
|
||||
}
|
||||
return message;
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to read message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
clientConnection.close();
|
||||
}
|
||||
|
||||
void onClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
void offClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.remove(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
private void checkIfClosed() {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
if (clientConnection.isClosed()) {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
import com.microsoft.playwright.impl.FormDataImpl;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* The {@code FormData} is used create form data that is sent via {@code APIRequestContext}.
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.options.FormData;
|
||||
* ...
|
||||
* FormData form = FormData.create()
|
||||
* .set("firstName", "John")
|
||||
* .set("lastName", "Doe")
|
||||
* .set("age", 30);
|
||||
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FormData {
|
||||
/**
|
||||
* Creates new instance of {@code FormData}.
|
||||
*/
|
||||
static FormData create() {
|
||||
return new FormDataImpl();
|
||||
}
|
||||
/**
|
||||
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param value Field value.
|
||||
*/
|
||||
FormData set(String name, String value);
|
||||
/**
|
||||
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param value Field value.
|
||||
*/
|
||||
FormData set(String name, boolean value);
|
||||
/**
|
||||
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param value Field value.
|
||||
*/
|
||||
FormData set(String name, int value);
|
||||
/**
|
||||
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param value Field value.
|
||||
*/
|
||||
FormData set(String name, Path value);
|
||||
/**
|
||||
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param value Field value.
|
||||
*/
|
||||
FormData set(String name, FilePayload value);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class Proxy {
|
||||
*/
|
||||
public String server;
|
||||
/**
|
||||
* Optional comma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
|
||||
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
|
||||
*/
|
||||
public String bypass;
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ public class Proxy {
|
||||
this.server = server;
|
||||
}
|
||||
/**
|
||||
* Optional comma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
|
||||
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
|
||||
*/
|
||||
public Proxy setBypass(String bypass) {
|
||||
this.bypass = bypass;
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
import com.microsoft.playwright.impl.RequestOptionsImpl;
|
||||
|
||||
/**
|
||||
* The {@code RequestOptions} allows to create form data to be sent via {@code APIRequestContext}.
|
||||
* <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,6 +19,5 @@ package com.microsoft.playwright.options;
|
||||
public enum WaitUntilState {
|
||||
LOAD,
|
||||
DOMCONTENTLOADED,
|
||||
NETWORKIDLE,
|
||||
COMMIT
|
||||
NETWORKIDLE
|
||||
}
|
||||
@@ -23,6 +23,7 @@ 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 {
|
||||
@@ -51,7 +52,8 @@ 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(HttpsConfiguratorImpl.class.getClassLoader().getResourceAsStream("resources/keys/keystore.jks"), password.toCharArray());
|
||||
ks.load(new FileInputStream("src/test/resources/keys/keystore.jks"), password.toCharArray());
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(ks, password.toCharArray());
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ 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<>());
|
||||
@@ -78,6 +79,7 @@ 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();
|
||||
}
|
||||
|
||||
@@ -98,14 +100,12 @@ 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,6 +165,8 @@ 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;
|
||||
}
|
||||
@@ -190,42 +192,32 @@ public class Server implements HttpHandler {
|
||||
if ("/".equals(path)) {
|
||||
path = "/index.html";
|
||||
}
|
||||
|
||||
// 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) {
|
||||
File file = new File(resourcesDir, path.substring(1));
|
||||
if (!file.exists()) {
|
||||
exchange.sendResponseHeaders(404, 0);
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("File not found: " + resourcePath);
|
||||
writer.write("File not found: " + file.getCanonicalPath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath)));
|
||||
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||
OutputStream output = body;
|
||||
exchange.getResponseHeaders().add("Content-Type", mimeType(file));
|
||||
OutputStream output = exchange.getResponseBody();
|
||||
if (gzipRoutes.contains(path)) {
|
||||
exchange.getResponseHeaders().add("Content-Encoding", "gzip");
|
||||
}
|
||||
try (InputStream input = resource) {
|
||||
try (FileInputStream input = new FileInputStream(file)) {
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
if (gzipRoutes.contains(path)) {
|
||||
output = new GZIPOutputStream(output);
|
||||
}
|
||||
copy(input, output);
|
||||
output.close();
|
||||
} catch (IOException e) {
|
||||
body.reset();
|
||||
try (Writer writer = new OutputStreamWriter(output)) {
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("Exception: " + e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
long contentLength = body.size();
|
||||
// -1 means no body, 0 means chunked encoding.
|
||||
exchange.sendResponseHeaders(200, contentLength == 0 ? -1 : contentLength);
|
||||
if (contentLength > 0) {
|
||||
exchange.getResponseBody().write(body.toByteArray());
|
||||
}
|
||||
exchange.getResponseBody().close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
private static String mimeType(File file) {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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);
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -57,7 +58,6 @@ 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,7 +346,21 @@ 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");
|
||||
assertEquals(0, cookies.size());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "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() || isFirefox()) {
|
||||
if (isChromium()) {
|
||||
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() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "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() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" }\n" +
|
||||
"]", cookies);
|
||||
}
|
||||
|
||||
+22
-20
@@ -32,8 +32,11 @@ 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() {
|
||||
@@ -43,35 +46,34 @@ public class TestBrowserContextCredentials extends TestBase {
|
||||
@Test
|
||||
void shouldWorkWithCorrectCredentials() {
|
||||
server.setAuth("/empty.html", "user", "pass");
|
||||
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());
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithWrongCredentials() {
|
||||
server.setAuth("/empty.html", "user", "pass");
|
||||
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());
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnResourceBody() {
|
||||
server.setAuth("/playground.html", "user", "pass");
|
||||
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"));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,683 +0,0 @@
|
||||
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,13 +16,10 @@
|
||||
|
||||
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;
|
||||
@@ -137,70 +134,4 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class TestBrowserContextStorageState extends TestBase {
|
||||
" 'expires':-1,\n" +
|
||||
" 'httpOnly':false,\n" +
|
||||
" 'secure':false,\n" +
|
||||
" 'sameSite':'" + (isChromium() || isFirefox() ? "Lax" : "None") + "'\n" +
|
||||
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
|
||||
" }],\n" +
|
||||
" 'origins':[\n" +
|
||||
" {\n" +
|
||||
|
||||
@@ -19,22 +19,21 @@ 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.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
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.*;
|
||||
|
||||
@@ -60,7 +59,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
|
||||
private static BrowserServer launchBrowserServer(BrowserType browserType) {
|
||||
try {
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
|
||||
Path dir = driver.getParent();
|
||||
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
|
||||
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
|
||||
@@ -227,20 +226,11 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
BrowserServer server = launchBrowserServer(browserType);
|
||||
Browser remote = browserType.connect(server.wsEndpoint);
|
||||
Page page = remote.newPage();
|
||||
boolean[] disconnected = {false};
|
||||
remote.onDisconnected(b -> disconnected[0] = true);
|
||||
server.kill();
|
||||
while (!disconnected[0]) {
|
||||
try {
|
||||
page.waitForTimeout(10);
|
||||
} catch (PlaywrightException e) {
|
||||
}
|
||||
}
|
||||
assertFalse(remote.isConnected());
|
||||
try {
|
||||
page.evaluate("1 + 1");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Browser has been closed"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Playwright connection closed"));
|
||||
}
|
||||
assertFalse(remote.isConnected());
|
||||
}
|
||||
@@ -258,6 +248,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
try {
|
||||
page.waitForTimeout(10);
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Playwright connection closed"));
|
||||
}
|
||||
}
|
||||
assertFalse(browser.isConnected());
|
||||
@@ -265,7 +256,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
page.waitForNavigation(() -> {});
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Playwright connection closed"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +271,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
page.navigate(server.PREFIX + "/one-style.html", new Page.NavigateOptions().setTimeout(60000));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Browser has been closed"));
|
||||
assertTrue(e.getMessage().contains("Playwright connection closed"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,36 +477,4 @@ 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,18 +111,16 @@ public class TestDownload extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldReportDownloadsWithAcceptDownloadsFalse() {
|
||||
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"));
|
||||
}
|
||||
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(616, 500);
|
||||
page.setViewportSize(500, 500);
|
||||
page.navigate(server.PREFIX + "/frames/nested-frames.html");
|
||||
Frame nestedFrame = page.frame("dos");
|
||||
assertNotNull(nestedFrame);
|
||||
|
||||
@@ -61,14 +61,14 @@ public class TestElementHandleConvenience extends TestBase {
|
||||
page.inputValue("#inner");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
|
||||
}
|
||||
ElementHandle handle2 = page.querySelector("#inner");
|
||||
try {
|
||||
handle2.inputValue();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an <input>, <textarea> or <select> element"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +95,14 @@ public class TestElementHandleConvenience extends TestBase {
|
||||
page.innerText("svg");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Not an HTMLElement"));
|
||||
}
|
||||
ElementHandle handle = page.querySelector("svg");
|
||||
try {
|
||||
handle.innerText();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLElement"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Not an HTMLElement"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user