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

Compare commits

...

29 Commits

Author SHA1 Message Date
Yury Semikhatsky 64115bfb60 devops: set version 1.22.0, driver to 1.22.0 (#929) 2022-05-13 15:31:22 -07:00
Yury Semikhatsky 15eefc54af feat: roll beta driver, remove layout selectors (#926) 2022-05-13 00:49:59 -07:00
Yury Semikhatsky abf245ccc7 feat: implement Locator.filter (#925) 2022-05-12 09:39:10 -07:00
Gabriel Gavrilov 10592ce5c7 Added Selectors and Keyboard Manipulation Example (#920) 2022-05-11 09:20:51 -07:00
Yury Semikhatsky ef7f50c48a feat: roll driver, implement locator filters (#921) 2022-05-10 10:43:30 -07:00
Andrey Lushnikov 473b1ce794 devops: mark docker image as playwright official (#889) 2022-05-06 19:07:44 -07:00
Yury Semikhatsky 98ecb7e0a0 test: unflake TestInstall.shouldThrowWhenBrowserPathIsInvalid (#916) 2022-05-03 13:50:20 -07:00
Yury Semikhatsky 04eb228813 devops: add workflow triggering internal tests (#915) 2022-05-02 18:01:40 -07:00
Max Schmitt 298e01ee80 chore: add arm64 Docker image (#890) 2022-05-02 14:46:05 +01:00
Yury Semikhatsky b6b54af13c Revert "Revert "Add Linux/arm64 support (#883)" (#892)" (#905)
This reverts commit 536af6b3d8.
2022-04-27 17:24:12 -07:00
Yury Semikhatsky b8d2ccae08 fix: respect isChecked options (#911) 2022-04-26 17:01:50 -07:00
Yury Semikhatsky 59e7c0cc94 fix: convert file path to absolute in setInputFiles (#903) 2022-04-15 12:11:52 -07:00
Yury Semikhatsky 54d0366b9e fix: get docker version from pom.xml (#899) (#901) 2022-04-12 12:15:06 -07:00
Andrey Lushnikov 9845a05544 devops: fix docker publish workflow (#898) 2022-04-12 11:40:21 -07:00
Yury Semikhatsky 41fd9a6f75 chore: bump dev version to 1.22 (#897) 2022-04-12 10:46:31 -07:00
Yury Semikhatsky 483cf0d473 chore: revert some inadvertent changes (#894) 2022-04-12 08:55:05 -07:00
Yury Semikhatsky 1681c410dd feat: large file uploads (#891) 2022-04-12 08:33:40 -07:00
Yury Semikhatsky 9f6860539a fix: links to documentation (#893) 2022-04-11 19:20:29 -07:00
Yury Semikhatsky 536af6b3d8 Revert "Add Linux/arm64 support (#883)" (#892) 2022-04-11 17:25:13 -07:00
Yury Semikhatsky 8ce193d144 chore: roll to 1.21 beta driver (#888) 2022-04-11 13:04:57 -07:00
Michael S. Fischer 7eddd2d2b2 Add Linux/arm64 support (#883) 2022-04-06 18:42:39 -07:00
uchagani 447578c582 fix(driver): set driver instance to null if an exception is thrown du… (#879) 2022-04-04 12:25:37 -07:00
Leonard Brünings 58013adfac fix(tracing): support explicit StartOptions().setSources(false) (#866) 2022-03-30 10:42:54 -07:00
Alex (Huy Tran) 43d12a7662 feat: enable loading alternative driver via system properties (#873) 2022-03-30 10:42:18 -07:00
Yury Semikhatsky 1deccbb55d chore: add logging to driver installation (#865) 2022-03-25 12:02:47 -07:00
Yury Semikhatsky 1b2d33402e docs: add driver update instructions 2022-03-18 09:00:34 -07:00
Yury Semikhatsky d315e7b5bf fix: docker publishing (#851) (#852) 2022-03-14 20:31:16 -07:00
Yury Semikhatsky 7dc22aa08a devops: do not cache mvn packages (#848) 2022-03-14 19:01:28 -07:00
Yury Semikhatsky 5b0ef8b7bf chore: update current version to 1.21.0-SNAPSHOT (#847) 2022-03-14 17:16:37 -07:00
72 changed files with 1829 additions and 431 deletions
@@ -3,6 +3,11 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
is_release:
required: true
type: boolean
description: "Is this a release image?"
branches:
- release-*
jobs:
+21 -32
View File
@@ -19,38 +19,35 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v2
with:
distribution: zulu
java-version: 8
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end -D test=*TestTracing*
env:
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_JAVA_SRC: src/test/java
- name: Run driver throw tests
run: mvn test -Dgroups=driverThrowTest --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
@@ -70,7 +67,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- uses: microsoft/playwright-github-action@v1
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
@@ -80,19 +77,18 @@ jobs:
with:
distribution: zulu
java-version: 8
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
- name: Run driver throw tests
run: mvn test -Dgroups=driverThrowTest --no-transfer-progress --fail-at-end
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
@@ -106,23 +102,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- uses: microsoft/playwright-github-action@v1
- 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: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
@@ -132,7 +122,6 @@ jobs:
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
@@ -0,0 +1,21 @@
name: "Internal Tests"
on:
push:
branches:
- main
- release-*
jobs:
trigger:
name: "trigger"
runs-on: ubuntu-20.04
steps:
- run: |
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GH_TOKEN}" \
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
https://api.github.com/repos/microsoft/playwright-internal/dispatches
env:
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
+1 -7
View File
@@ -20,13 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- uses: microsoft/playwright-github-action@v1
- name: Download drivers
run: scripts/download_driver_for_all_platforms.sh
- name: Regenerate APIs
+14 -1
View File
@@ -8,7 +8,7 @@ Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubun
just run the following command:
```sh
sudo apt-get install git openjdk-11-jdk maven
sudo apt-get install git openjdk-11-jdk maven unzip
```
### Getting the Code
@@ -49,6 +49,19 @@ Java interfaces for the current driver run the following commands:
./scripts/generate_api.sh
```
#### Updating driver version
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
```bash
cat > scripts/CLI_VERSION
<paste new version>
^D
./scripts/download_driver_for_all_platforms.sh -f
./scripts/generate_api.sh
./scripts/update_readme.sh
```
### Code Style
- We try to follow [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)
+2 -2
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->101.0.4929.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->102.0.5005.40<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->97.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->99.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -37,11 +37,13 @@ public class DriverJar extends Driver {
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
driverTempDir.toFile().deleteOnExit();
logMessage("created DriverJar: " + driverTempDir);
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
extractDriverToTempDir();
logMessage("extracted driver from jar to " + driverPath());
if (installBrowsers)
installBrowsers(env);
}
@@ -137,11 +139,17 @@ public class DriverJar extends Driver {
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").toLowerCase();
if (name.contains("windows")) {
return "win32_x64";
}
if (name.contains("linux")) {
return "linux";
if (arch.equals("aarch64")) {
return "linux-arm64";
} else {
return "linux";
}
}
if (name.contains("mac os x")) {
return "mac";
@@ -18,16 +18,19 @@ 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.*;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestInstall {
@@ -36,6 +39,20 @@ public class TestInstall {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
System.clearProperty("playwright.driver.tmpdir");
// Clear system property to ensure that the default driver is loaded.
System.clearProperty("playwright.driver.impl");
}
@Test
@Tags({@Tag("isolated"), @Tag("driverThrowTest")})
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) {
Map<String,String> env = new HashMap<>();
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.127");
// Make sure the browsers are not installed yet by pointing at an empty dir.
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
}
@Test
@@ -57,4 +74,19 @@ public class TestInstall {
DriverJar driver = new DriverJar();
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
}
@Test
void playwrightDriverDefaultImpl() {
assertDoesNotThrow(() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
}
@Test
void playwrightDriverAlternativeImpl() {
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
assertEquals("Failed to create driver", thrown.getMessage());
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -18,8 +18,11 @@ package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Map;
import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
/**
* This class provides access to playwright-cli. It can be either preinstalled
* in the host system and its path is passed as a system property or it can be
@@ -31,6 +34,7 @@ public abstract class Driver {
private static class PreinstalledDriver extends Driver {
private final Path driverDir;
PreinstalledDriver(Path driverDir) {
logMessage("created PreinstalledDriver: " + driverDir);
this.driverDir = driverDir;
}
@@ -49,8 +53,11 @@ public abstract class Driver {
if (instance == null) {
try {
instance = createDriver();
logMessage("initializing driver");
instance.initialize(env, installBrowsers);
logMessage("driver initialized.");
} catch (Exception exception) {
instance = null;
throw new RuntimeException("Failed to create driver", exception);
}
}
@@ -92,9 +99,16 @@ public abstract class Driver {
return new PreinstalledDriver(Paths.get(pathFromProperty));
}
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:install " + message);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
class DriverLogging {
private static final boolean isEnabled;
static {
String debug = System.getenv("DEBUG");
isEnabled = (debug != null) && debug.contains("pw:install");
}
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
static void logWithTimestamp(String message) {
if (!isEnabled) {
return;
}
// This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat);
System.err.println(timestamp + " " + message);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -0,0 +1,35 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
public class SelectorsAndKeyboardManipulation {
public static void main(String[] args) {
try(Playwright playwright = Playwright.create()) {
Browser browser = playwright.firefox().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://playwright.dev/java/");
page.locator("text=SearchK").click();
page.locator("[placeholder=\"Search docs\"]").fill("getting started");
page.locator("div[role=\"button\"]:has-text(\"CancelIntroductionGetting startedInstallationGetting startedUsageGetting start\")").click();
page.waitForSelector("h1:has-text(\"Getting started\")"); // Waits for the new page to load before screenshotting.
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("Screenshot.png")));
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -21,9 +21,9 @@ 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>.
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance which
* in turn can be used for sending web requests. An instance of this class can be obtained via {@link Playwright#request
* Playwright.request()}. For more information see {@code APIRequestContext}.
*/
public interface APIRequest {
class NewContextOptions {
@@ -21,9 +21,24 @@ 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.
* environment or the service to your e2e test.
*
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage with the
* browser context and can be accessed via {@link BrowserContext#request BrowserContext.request()} or {@link Page#request
* Page.request()}. It is also possible to create a new APIRequestContext instance manually by calling {@link
* APIRequest#newContext APIRequest.newContext()}.
*
* <p> **Cookie management**
*
* <p> {@code APIRequestContext} retuned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
* log in using this API, your e2e test will be logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you shoud create a new {@code APIRequestContext} by calling
* {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own isolated cookie
* storage.
*/
public interface APIRequestContext {
class StorageStateOptions {
@@ -1017,8 +1017,9 @@ public interface Browser extends AutoCloseable {
Page newPage(NewPageOptions options);
/**
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
* which is a low-level chromium-specific debugging tool. API to control <a
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
@@ -1036,8 +1037,9 @@ public interface Browser extends AutoCloseable {
}
/**
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
* which is a low-level chromium-specific debugging tool. API to control <a
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
@@ -1053,8 +1055,9 @@ public interface Browser extends AutoCloseable {
}
/**
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
* which is a low-level chromium-specific debugging tool. API to control <a
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
@@ -1070,8 +1073,9 @@ public interface Browser extends AutoCloseable {
void startTracing(Page page, StartTracingOptions options);
/**
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
* which is a low-level chromium-specific debugging tool. API to control <a
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
*
* <p> Returns the buffer with trace data.
*/
@@ -497,7 +497,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
@@ -524,7 +523,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
@@ -52,8 +52,7 @@ public interface BrowserType {
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
*/
public Double timeout;
@@ -73,8 +72,7 @@ public interface BrowserType {
return this;
}
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
*/
public ConnectOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -971,7 +969,7 @@ public interface BrowserType {
}
}
/**
* This methods attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
@@ -979,13 +977,13 @@ public interface BrowserType {
return connect(wsEndpoint, null);
}
/**
* This methods attaches Playwright to an existing browser instance.
* This method attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
Browser connect(String wsEndpoint, ConnectOptions options);
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
@@ -998,7 +996,7 @@ public interface BrowserType {
return connectOverCDP(endpointURL, null);
}
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
@@ -19,7 +19,28 @@ package com.microsoft.playwright;
import java.util.*;
/**
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event.
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event. For
* each console messages logged in the page there will be corresponding event in the Playwright context.
* <pre>{@code
* // Listen for all System.out.printlns
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
*
* // Listen for all console events and handle errors
* page.onConsoleMessage(msg -> {
* if ("error".equals(msg.type()))
* System.out.println("Error text: " + msg.text());
* });
*
* // Get the next System.out.println
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
* // Issue console.log inside the page
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
* });
*
* // Deconstruct console.log arguments
* msg.args().get(0).jsonValue() // hello
* msg.args().get(1).jsonValue() // 42
* }</pre>
*/
public interface ConsoleMessage {
/**
@@ -576,8 +576,15 @@ public interface ElementHandle extends JSHandle {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotAnimations animations;
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
@@ -598,6 +605,12 @@ public interface ElementHandle extends JSHandle {
* The quality of the image, between 0-100. Not applicable to {@code png} images.
*/
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -616,11 +629,21 @@ public interface ElementHandle extends JSHandle {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotOptions setAnimations(ScreenshotAnimations animations) {
this.animations = animations;
return this;
}
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotOptions setCaret(ScreenshotCaret caret) {
this.caret = caret;
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
@@ -653,6 +676,15 @@ public interface ElementHandle extends JSHandle {
this.quality = quality;
return this;
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -1569,13 +1601,21 @@ public interface ElementHandle extends JSHandle {
*/
String innerText();
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*/
default String inputValue() {
return inputValue(null);
}
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*/
String inputValue(InputValueOptions options);
/**
@@ -1677,19 +1717,27 @@ public interface ElementHandle extends JSHandle {
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* Returns the buffer with the captured screenshot.
* This method captures a screenshot of the page, clipped to the size and position of this particular element. If the
* element is covered by other elements, it will not be actually visible on the screenshot. If the element is a scrollable
* container, only the currently scrolled content will be visible on the screenshot.
*
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
*
* <p> Returns the buffer with the captured screenshot.
*/
default byte[] screenshot() {
return screenshot(null);
}
/**
* Returns the buffer with the captured screenshot.
* This method captures a screenshot of the page, clipped to the size and position of this particular element. If the
* element is covered by other elements, it will not be actually visible on the screenshot. If the element is a scrollable
* container, only the currently scrolled content will be visible on the screenshot.
*
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
*
* <p> Returns the buffer with the captured screenshot.
*/
byte[] screenshot(ScreenshotOptions options);
/**
@@ -2039,6 +2087,10 @@ public interface ElementHandle extends JSHandle {
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
* the element and selects all its text content.
*
* <p> If the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, focuses and selects text
* in the control instead.
*/
default void selectText() {
selectText(null);
@@ -2046,6 +2098,10 @@ public interface ElementHandle extends JSHandle {
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
* the element and selects all its text content.
*
* <p> If the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, focuses and selects text
* in the control instead.
*/
void selectText(SelectTextOptions options);
/**
@@ -2089,75 +2145,99 @@ public interface ElementHandle extends JSHandle {
*/
void setChecked(boolean checked, SetCheckedOptions options);
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(Path files) {
setInputFiles(files, null);
}
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(Path files, SetInputFilesOptions options);
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(Path[] files) {
setInputFiles(files, null);
}
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(Path[] files, SetInputFilesOptions options);
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(FilePayload files) {
setInputFiles(files, null);
}
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(FilePayload files, SetInputFilesOptions options);
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(FilePayload[] files) {
setInputFiles(files, null);
}
/**
* This method expects {@code elementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
/**
@@ -74,50 +74,50 @@ public interface FileChooser {
Page page();
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
default void setFiles(Path files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
void setFiles(Path files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
default void setFiles(Path[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
void setFiles(Path[] files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
default void setFiles(FilePayload files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
void setFiles(FilePayload files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
default void setFiles(FilePayload[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
void setFiles(FilePayload[] files, SetFilesOptions options);
}
@@ -1226,8 +1226,9 @@ public interface Frame {
*/
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
@@ -1242,16 +1243,18 @@ public interface Frame {
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(String hasText) {
this.hasText = hasText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
@@ -1921,7 +1924,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public Object url;
@@ -1948,7 +1951,7 @@ public interface Frame {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(String url) {
@@ -1957,7 +1960,7 @@ public interface Frame {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(Pattern url) {
@@ -1966,7 +1969,7 @@ public interface Frame {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(Predicate<String> url) {
@@ -2865,7 +2868,11 @@ public interface Frame {
*/
String innerText(String selector, InnerTextOptions options);
/**
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -2874,7 +2881,11 @@ public interface Frame {
return inputValue(selector, null);
}
/**
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -2993,6 +3004,8 @@ public interface Frame {
* 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> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -3004,6 +3017,8 @@ public interface Frame {
* 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> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
@@ -3536,11 +3551,14 @@ public interface Frame {
*/
void setContent(String html, SetContentOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3549,22 +3567,28 @@ public interface Frame {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, Path files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3573,22 +3597,28 @@ public interface Frame {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, Path[] files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3597,22 +3627,28 @@ public interface Frame {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -3621,11 +3657,14 @@ public interface Frame {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -4064,7 +4103,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(String url) {
@@ -4078,7 +4117,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(String url, WaitForURLOptions options);
@@ -4090,7 +4129,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(Pattern url) {
@@ -4104,7 +4143,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(Pattern url, WaitForURLOptions options);
@@ -4116,7 +4155,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(Predicate<String> url) {
@@ -4130,7 +4169,7 @@ public interface Frame {
* }</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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(Predicate<String> url, WaitForURLOptions options);
@@ -57,8 +57,9 @@ public interface FrameLocator {
*/
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
@@ -73,16 +74,18 @@ public interface FrameLocator {
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(String hasText) {
this.hasText = hasText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
@@ -122,7 +125,7 @@ public interface FrameLocator {
*/
Locator locator(String selector, LocatorOptions options);
/**
* Returns locator to the n-th matching frame.
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
*/
FrameLocator nth(int index);
}
@@ -590,6 +590,50 @@ public interface Locator {
return this;
}
}
class FilterOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator has;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public FilterOptions setHas(Locator has) {
this.has = has;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public FilterOptions setHasText(String hasText) {
this.hasText = hasText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public FilterOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
class FocusOptions {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
@@ -872,8 +916,9 @@ public interface Locator {
*/
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
@@ -888,16 +933,18 @@ public interface Locator {
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(String hasText) {
this.hasText = hasText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
@@ -956,8 +1003,15 @@ public interface Locator {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotAnimations animations;
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
@@ -978,6 +1032,12 @@ public interface Locator {
* The quality of the image, between 0-100. Not applicable to {@code png} images.
*/
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -996,11 +1056,21 @@ public interface Locator {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotOptions setAnimations(ScreenshotAnimations animations) {
this.animations = animations;
return this;
}
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotOptions setCaret(ScreenshotCaret caret) {
this.caret = caret;
return this;
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* {@code #FF00FF} that completely covers its bounding box.
@@ -1033,6 +1103,15 @@ public interface Locator {
this.quality = quality;
return this;
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -2039,6 +2118,16 @@ public interface Locator {
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
*/
void fill(String value, FillOptions options);
/**
* This method narrows existing locator according to the options, for example filters by text.
*/
default Locator filter() {
return filter(null);
}
/**
* This method narrows existing locator according to the options, for example filters by text.
*/
Locator filter(FilterOptions options);
/**
* Returns locator to the first matching element.
*/
@@ -2139,13 +2228,21 @@ public interface Locator {
*/
String innerText(InnerTextOptions options);
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*/
default String inputValue() {
return inputValue(null);
}
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*/
String inputValue(InputValueOptions options);
/**
@@ -2217,7 +2314,8 @@ 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. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* @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.
@@ -2226,14 +2324,15 @@ public interface Locator {
return locator(selector, null);
}
/**
* 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. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
*/
Locator locator(String selector, LocatorOptions options);
/**
* Returns locator to the n-th matching element.
* Returns locator to the n-th matching element. It's zero based, {@code nth(0)} selects the first element.
*/
Locator nth(int index);
/**
@@ -2291,19 +2390,27 @@ public interface Locator {
*/
void press(String key, PressOptions options);
/**
* Returns the buffer with the captured screenshot.
* This method captures a screenshot of the page, clipped to the size and position of a particular element matching the
* locator. If the element is covered by other elements, it will not be actually visible on the screenshot. If the element
* is a scrollable container, only the currently scrolled content will be visible on the screenshot.
*
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
*
* <p> Returns the buffer with the captured screenshot.
*/
default byte[] screenshot() {
return screenshot(null);
}
/**
* Returns the buffer with the captured screenshot.
* This method captures a screenshot of the page, clipped to the size and position of a particular element matching the
* locator. If the element is covered by other elements, it will not be actually visible on the screenshot. If the element
* is a scrollable container, only the currently scrolled content will be visible on the screenshot.
*
* <p> This method waits for the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then
* scrolls element into view before taking a screenshot. If the element is detached from DOM, the method throws an error.
*
* <p> Returns the buffer with the captured screenshot.
*/
byte[] screenshot(ScreenshotOptions options);
/**
@@ -2647,6 +2754,10 @@ public interface Locator {
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
* the element and selects all its text content.
*
* <p> If the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, focuses and selects text
* in the control instead.
*/
default void selectText() {
selectText(null);
@@ -2654,6 +2765,10 @@ public interface Locator {
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks, then focuses
* the element and selects all its text content.
*
* <p> If the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, focuses and selects text
* in the control instead.
*/
void selectText(SelectTextOptions options);
/**
@@ -2697,75 +2812,99 @@ public interface Locator {
*/
void setChecked(boolean checked, SetCheckedOptions options);
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(Path files) {
setInputFiles(files, null);
}
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(Path files, SetInputFilesOptions options);
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(Path[] files) {
setInputFiles(files, null);
}
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(Path[] files, SetInputFilesOptions options);
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(FilePayload files) {
setInputFiles(files, null);
}
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(FilePayload files, SetInputFilesOptions options);
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
default void setInputFiles(FilePayload[] files) {
setInputFiles(files, null);
}
/**
* This method expects {@code element} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code Locator} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*/
void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
/**
@@ -122,12 +122,12 @@ public interface Mouse {
}
class MoveOptions {
/**
* defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends intermediate {@code mousemove} events.
*/
public Integer steps;
/**
* defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends intermediate {@code mousemove} events.
*/
public MoveOptions setSteps(int steps) {
this.steps = steps;
@@ -125,6 +125,11 @@ public interface Page extends AutoCloseable {
* either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()} the dialog - otherwise the page
* will <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for
* the dialog, and actions like click will never finish.
* <pre>{@code
* page.onDialog(dialog -> {
* dialog.accept();
* });
* }</pre>
*
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} listeners are present, all dialogs are automatically dismissed.
*/
@@ -209,6 +214,15 @@ public interface Page extends AutoCloseable {
/**
* Emitted when an uncaught exception happens within the page.
* <pre>{@code
* // Log all uncaught errors to the terminal
* page.onPageError(exception -> {
* System.out.println("Uncaught exception: " + exception);
* });
*
* // Navigate to a page with an exception.
* page.navigate("data:text/html,<script>throw new Error('Test')</script>");
* }</pre>
*/
void onPageError(Consumer<String> handler);
/**
@@ -251,6 +265,11 @@ public interface Page extends AutoCloseable {
/**
* Emitted when a request fails, for example by timing out.
* <pre>{@code
* page.onRequestFailed(request -> {
* System.out.println(request.url() + " " + request.failure());
* });
* }</pre>
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@link Page#onRequestFinished Page.onRequestFinished()} event and not with {@link Page#onRequestFailed
@@ -1647,8 +1666,9 @@ public interface Page extends AutoCloseable {
*/
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public Object hasText;
@@ -1663,16 +1683,18 @@ public interface Page extends AutoCloseable {
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>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(String hasText) {
this.hasText = hasText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
@@ -1983,8 +2005,15 @@ public interface Page extends AutoCloseable {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotAnimations animations;
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotCaret caret;
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
*/
@@ -2014,6 +2043,12 @@ public interface Page extends AutoCloseable {
* The quality of the image, between 0-100. Not applicable to {@code png} images.
*/
public Integer quality;
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -2032,11 +2067,21 @@ public interface Page extends AutoCloseable {
* <li> finite animations are fast-forwarded to completion, so they'll fire {@code transitionend} event.</li>
* <li> infinite animations are canceled to initial state, and then played over after the screenshot.</li>
* </ul>
*
* <p> Defaults to {@code "allow"} that leaves animations untouched.
*/
public ScreenshotOptions setAnimations(ScreenshotAnimations animations) {
this.animations = animations;
return this;
}
/**
* When set to {@code "hide"}, screenshot will hide text caret. When set to {@code "initial"}, text caret behavior will not be changed.
* Defaults to {@code "hide"}.
*/
public ScreenshotOptions setCaret(ScreenshotCaret caret) {
this.caret = caret;
return this;
}
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
*/
@@ -2090,6 +2135,15 @@ public interface Page extends AutoCloseable {
this.quality = quality;
return this;
}
/**
* When set to {@code "css"}, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using {@code "device"} option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to {@code "device"}.
*/
public ScreenshotOptions setScale(ScreenshotScale scale) {
this.scale = scale;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
@@ -2794,7 +2848,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public Object url;
@@ -2821,7 +2875,7 @@ public interface Page extends AutoCloseable {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(String url) {
@@ -2830,7 +2884,7 @@ public interface Page extends AutoCloseable {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(Pattern url) {
@@ -2839,7 +2893,7 @@ public interface Page extends AutoCloseable {
}
/**
* 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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
public WaitForNavigationOptions setUrl(Predicate<String> url) {
@@ -4147,8 +4201,8 @@ public interface Page extends AutoCloseable {
*/
Response goForward(GoForwardOptions options);
/**
* 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 first
* non-redirect response.
*
* <p> The method will throw an error if:
* <ul>
@@ -4179,8 +4233,8 @@ public interface Page extends AutoCloseable {
return navigate(url, null);
}
/**
* 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 first
* non-redirect response.
*
* <p> The method will throw an error if:
* <ul>
@@ -4283,7 +4337,11 @@ public interface Page extends AutoCloseable {
*/
String innerText(String selector, InnerTextOptions options);
/**
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -4292,7 +4350,11 @@ public interface Page extends AutoCloseable {
return inputValue(selector, null);
}
/**
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
* Returns {@code input.value} for the selected {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> Throws for non-input elements. However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, returns the value of the
* control.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -4412,6 +4474,8 @@ public interface Page extends AutoCloseable {
* 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> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <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
@@ -4425,6 +4489,8 @@ public interface Page extends AutoCloseable {
* 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> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* <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
@@ -4684,7 +4750,9 @@ public interface Page extends AutoCloseable {
*/
Response reload(ReloadOptions options);
/**
* API testing helper associated with this page. Requests made with this API will use page cookies.
* API testing helper associated with this page. This method returns the same instance as {@link BrowserContext#request
* BrowserContext.request()} on the page's context. See {@link BrowserContext#request BrowserContext.request()} for more
* details.
*/
APIRequestContext request();
/**
@@ -5496,11 +5564,14 @@ public interface Page extends AutoCloseable {
*/
void setExtraHTTPHeaders(Map<String, String> headers);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -5509,22 +5580,28 @@ public interface Page extends AutoCloseable {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, Path files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -5533,22 +5610,28 @@ public interface Page extends AutoCloseable {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, Path[] files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -5557,22 +5640,28 @@ public interface Page extends AutoCloseable {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
*/
void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options);
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -5581,11 +5670,14 @@ public interface Page extends AutoCloseable {
setInputFiles(selector, files, null);
}
/**
* This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>.
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then they
* are resolved relative to the the current working directory. For empty array, clears the selected files.
* <p> This method expects {@code selector} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
* inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, targets the control
* instead.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See
* <a href="https://playwright.dev/java/docs/selectors">working with selectors</a> for more details.
@@ -6545,7 +6637,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(String url) {
@@ -6561,7 +6653,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(String url, WaitForURLOptions options);
@@ -6575,7 +6667,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(Pattern url) {
@@ -6591,7 +6683,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(Pattern url, WaitForURLOptions options);
@@ -6605,7 +6697,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
default void waitForURL(Predicate<String> url) {
@@ -6621,7 +6713,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
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
void waitForURL(Predicate<String> url, WaitForURLOptions options);
@@ -22,6 +22,8 @@ import java.util.*;
/**
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
*
* <p> Learn more about <a href="https://playwright.dev/java/docs/network">networking</a>.
*/
public interface Route {
class ResumeOptions {
@@ -56,7 +56,8 @@ public interface Tracing {
public Boolean snapshots;
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
* other platforms).
*/
public Boolean sources;
/**
@@ -92,7 +93,8 @@ public interface Tracing {
}
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
* other platforms).
*/
public StartOptions setSources(boolean sources) {
this.sources = sources;
@@ -328,7 +328,11 @@ public interface LocatorAssertions {
*/
void isChecked(IsCheckedOptions options);
/**
* Ensures the {@code Locator} points to a disabled element.
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
* via <a
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
* <pre>{@code
* assertThat(page.locator("button.submit")).isDisabled();
* }</pre>
@@ -337,7 +341,11 @@ public interface LocatorAssertions {
isDisabled(null);
}
/**
* Ensures the {@code Locator} points to a disabled element.
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
* via <a
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
* <pre>{@code
* assertThat(page.locator("button.submit")).isDisabled();
* }</pre>
@@ -409,7 +417,7 @@ public interface LocatorAssertions {
void isFocused(IsFocusedOptions options);
/**
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
* <pre>{@code
* assertThat(page.locator(".my-element")).isHidden();
* }</pre>
@@ -419,25 +427,27 @@ public interface LocatorAssertions {
}
/**
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
* <pre>{@code
* assertThat(page.locator(".my-element")).isHidden();
* }</pre>
*/
void isHidden(IsHiddenOptions options);
/**
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
* node.
* <pre>{@code
* assertThat(page.locator(".my-element")).toBeVisible();
* assertThat(page.locator(".my-element")).isVisible();
* }</pre>
*/
default void isVisible() {
isVisible(null);
}
/**
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
* node.
* <pre>{@code
* assertThat(page.locator(".my-element")).toBeVisible();
* assertThat(page.locator(".my-element")).isVisible();
* }</pre>
*/
void isVisible(IsVisibleOptions options);
@@ -560,4 +560,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
listeners.notify(EventType.CLOSE, this);
}
WritableStream createTempFile(String name) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
}
@@ -302,6 +302,9 @@ public class Connection {
case "Worker":
result = new WorkerImpl(parent, type, guid, initializer);
break;
case "WritableStream":
result = new WritableStream(parent, type, guid, initializer);
break;
default:
throw new PlaywrightException("Unknown type " + type);
}
@@ -33,7 +33,8 @@ 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.*;
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -299,7 +300,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public Frame ownerFrame() {
public FrameImpl ownerFrame() {
return withLogging("ElementHandle.ownerFrame", () -> {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
@@ -455,7 +456,24 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
setInputFiles(Utils.toFilePayloads(files), options);
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
FrameImpl frame = ownerFrame();
if (frame == null) {
throw new Error("Cannot set input files to detached element");
}
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, frame.page().context());
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(Utils.toFilePayloads(files), options);
}
}
@Override
@@ -469,6 +487,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files);
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -58,7 +58,8 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(Path[] files, SetFilesOptions options) {
setFiles(Utils.toFilePayloads(files), options);
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
}
@Override
@@ -69,6 +70,6 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -23,6 +23,7 @@ import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -31,7 +32,7 @@ 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.impl.Utils.*;
import static com.microsoft.playwright.options.WaitUntilState.*;
import static com.microsoft.playwright.impl.Serialization.*;
@@ -686,21 +687,32 @@ public class FrameImpl extends ChannelOwner implements Frame {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
}
}
@Override
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
setInputFiles(selector, new FilePayload[]{files}, options);
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
setInputFiles(selector, Utils.toFilePayloads(files), options);
}
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files);
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -257,7 +257,8 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void isChecked(IsCheckedOptions options) {
expectTrue("to.be.checked", "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
expectTrue(expression, "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -1,6 +1,5 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
@@ -9,8 +8,11 @@ import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import com.microsoft.playwright.options.WaitForSelectorState;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
@@ -22,6 +24,43 @@ class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
private static class Filters {
private final Map<Field, String> filterFieldToEngine = new LinkedHashMap<>();
private void addFilter(String name, String engine) throws NoSuchFieldException {
filterFieldToEngine.put(LocatorOptions.class.getField(name), engine);
}
{
try {
addFilter("has", "has");
// addFilter("leftOf", "left-of");
// addFilter("rightOf", "right-of");
// addFilter("above", "above");
// addFilter("below", "below");
// addFilter("near", "near");
} catch (NoSuchFieldException e) {
throw new InternalError(e);
}
}
String addFiltersToSelector(String selector, LocatorOptions options, Frame frame) {
try {
for (Map.Entry<Field, String> p : filterFieldToEngine.entrySet()) {
LocatorImpl filter = (LocatorImpl) p.getKey().get(options);
if (filter == null) {
continue;
}
if (filter.frame != frame) {
throw new PlaywrightException("Inner '" + p.getKey().getName() + "' locator must belong to the same frame.");
}
selector += " >> " + p.getValue() + "=" + gson().toJson(filter.selector);
}
} catch (IllegalAccessException e) {
throw new PlaywrightException("Unexpected options", e);
}
return selector;
}
}
private static final Filters filters = new Filters();
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
if (options != null) {
@@ -34,14 +73,7 @@ class LocatorImpl implements Locator {
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);
}
selector = filters.addFiltersToSelector(selector, options, frame);
}
this.selector = selector;
}
@@ -170,6 +202,11 @@ class LocatorImpl implements Locator {
frame.fill(selector, value, convertType(options, Frame.FillOptions.class).setStrict(true));
}
@Override
public Locator filter(FilterOptions options) {
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
}
@Override
public Locator first() {
return new LocatorImpl(frame, selector + " >> nth=0", null);
@@ -43,6 +43,8 @@ class SerializedValue{
}
O[] o;
Number h;
Integer id;
Integer ref;
}
class SerializedArgument{
@@ -42,6 +42,8 @@ class Serialization {
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
.registerTypeAdapter(ScreenshotAnimations.class, new ToLowerCaseSerializer<ScreenshotAnimations>())
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
.registerTypeAdapter(ScreenshotScale.class, new ToLowerCaseSerializer<ScreenshotScale>())
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
@@ -69,82 +71,128 @@ class Serialization {
return result;
}
private static SerializedValue serializeValue(Object value, List<JSHandleImpl> handles, int depth) {
if (depth > 100) {
throw new PlaywrightException("Maximum argument depth exceeded");
private static class ValueSerializer {
// hashCode() of a map containing itself as a key will throw stackoverflow exception,
// so we user wrappers.
private static class HashableValue {
final Object value;
HashableValue(Object value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
return value == ((HashableValue) o).value;
}
@Override
public int hashCode() {
return System.identityHashCode(value);
}
}
SerializedValue result = new SerializedValue();
if (value instanceof JSHandleImpl) {
result.h = handles.size();
handles.add((JSHandleImpl) value);
private final Map<HashableValue, Integer> valueToId = new HashMap<>();
private int lastId = 0;
private final List<JSHandleImpl> handles = new ArrayList<>();
private final SerializedValue serializedValue;
ValueSerializer(Object value) {
serializedValue = serializeValue(value);
}
SerializedArgument toSerializedArgument() {
SerializedArgument result = new SerializedArgument();
result.value = serializedValue;
result.handles = new Channel[handles.size()];
int i = 0;
for (JSHandleImpl handle : handles) {
result.handles[i] = new Channel();
result.handles[i].guid = handle.guid;
++i;
}
return result;
}
if (value == null) {
result.v = "undefined";
} else if (value instanceof Double) {
double d = ((Double) value);
if (d == Double.POSITIVE_INFINITY) {
result.v = "Infinity";
} else if (d == Double.NEGATIVE_INFINITY) {
result.v = "-Infinity";
} else if (d == -0) {
result.v = "-0";
} else if (Double.isNaN(d)) {
result.v = "NaN";
private SerializedValue serializeValue(Object value) {
SerializedValue result = new SerializedValue();
if (value instanceof JSHandleImpl) {
result.h = handles.size();
handles.add((JSHandleImpl) value);
return result;
}
if (value == null) {
result.v = "undefined";
} else if (value instanceof Double) {
double d = ((Double) value);
if (d == Double.POSITIVE_INFINITY) {
result.v = "Infinity";
} else if (d == Double.NEGATIVE_INFINITY) {
result.v = "-Infinity";
} else if (d == -0) {
result.v = "-0";
} else if (Double.isNaN(d)) {
result.v = "NaN";
} else {
result.n = d;
}
} else if (value instanceof Boolean) {
result.b = (Boolean) value;
} else if (value instanceof Integer) {
result.n = (Integer) value;
} else if (value instanceof String) {
result.s = (String) value;
} else {
result.n = d;
HashableValue mapKey = new HashableValue(value);
Integer id = valueToId.get(mapKey);
if (id != null) {
result.ref = id;
} else {
result.id = ++lastId;
valueToId.put(mapKey, lastId);
if (value instanceof List) {
List<SerializedValue> list = new ArrayList<>();
for (Object o : (List<?>) value) {
list.add(serializeValue(o));
}
result.a = list.toArray(new SerializedValue[0]);
} else if (value instanceof Map) {
List<SerializedValue.O> list = new ArrayList<>();
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) value;
for (Map.Entry<String, ?> e : map.entrySet()) {
SerializedValue.O o = new SerializedValue.O();
o.k = e.getKey();
o.v = serializeValue(e.getValue());
list.add(o);
}
result.o = list.toArray(new SerializedValue.O[0]);
} else if (value instanceof Object[]) {
List<SerializedValue> list = new ArrayList<>();
for (Object o : (Object[]) value) {
list.add(serializeValue(o));
}
result.a = list.toArray(new SerializedValue[0]);
} else {
throw new PlaywrightException("Unsupported type of argument: " + value);
}
}
}
} else if (value instanceof Boolean) {
result.b = (Boolean) value;
} else if (value instanceof Integer) {
result.n = (Integer) value;
} else if (value instanceof String) {
result.s = (String) value;
} else if (value instanceof List) {
List<SerializedValue> list = new ArrayList<>();
for (Object o : (List<?>) value) {
list.add(serializeValue(o, handles, depth + 1));
}
result.a = list.toArray(new SerializedValue[0]);
} else if (value instanceof Map) {
List<SerializedValue.O> list = new ArrayList<>();
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) value;
for (Map.Entry<String, ?> e : map.entrySet()) {
SerializedValue.O o = new SerializedValue.O();
o.k = e.getKey();
o.v = serializeValue(e.getValue(), handles, depth + 1);
list.add(o);
}
result.o = list.toArray(new SerializedValue.O[0]);
} else if (value instanceof Object[]) {
List<SerializedValue> list = new ArrayList<>();
for (Object o : (Object[]) value) {
list.add(serializeValue(o, handles, depth + 1));
}
result.a = list.toArray(new SerializedValue[0]);
} else {
throw new PlaywrightException("Unsupported type of argument: " + value);
return result;
}
return result;
}
static SerializedArgument serializeArgument(Object arg) {
SerializedArgument result = new SerializedArgument();
List<JSHandleImpl> handles = new ArrayList<>();
result.value = serializeValue(arg, handles, 0);
result.handles = new Channel[handles.size()];
int i = 0;
for (JSHandleImpl handle : handles) {
result.handles[i] = new Channel();
result.handles[i].guid = handle.guid;
++i;
}
return result;
return new ValueSerializer(arg).toSerializedArgument();
}
static <T> T deserialize(SerializedValue value) {
return deserialize(value, new HashMap<>());
}
@SuppressWarnings("unchecked")
static <T> T deserialize(SerializedValue value) {
private static <T> T deserialize(SerializedValue value, Map<Integer, Object> idToValue) {
if (value.ref != null) {
return (T) idToValue.get(value.ref);
}
if (value.n != null) {
if (value.n.doubleValue() == (double) value.n.intValue()) {
return (T) Integer.valueOf(value.n.intValue());
@@ -175,15 +223,17 @@ class Serialization {
}
if (value.a != null) {
List<Object> list = new ArrayList<>();
idToValue.put(value.id, list);
for (SerializedValue v : value.a) {
list.add(deserialize(v));
list.add(deserialize(v, idToValue));
}
return (T) list;
}
if (value.o != null) {
Map<String, Object> map = new LinkedHashMap<>();
idToValue.put(value.id, map);
for (SerializedValue.O o : value.o) {
map.put(o.k, deserialize(o.v));
map.put(o.k, deserialize(o.v, idToValue));
}
return (T) map;
}
@@ -210,6 +260,14 @@ class Serialization {
}
}
static JsonArray toJsonArray(Path[] files) {
JsonArray jsonFiles = new JsonArray();
for (Path p : files) {
jsonFiles.add(p.toAbsolutePath().toString());
}
return jsonFiles;
}
static JsonArray toJsonArray(FilePayload[] files) {
JsonArray jsonFiles = new JsonArray();
for (FilePayload p : files) {
@@ -28,7 +28,6 @@ import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl extends ChannelOwner implements Tracing {
LocalUtils localUtils;
boolean isRemote;
private boolean includeSources;
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -90,7 +89,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
options = new StartOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
includeSources = options.sources != null;
boolean includeSources = options.sources != null && options.sources;
if (includeSources) {
if (!connection.isCollectingStacks()) {
throw new PlaywrightException("Source root directory must be specified via PLAYWRIGHT_JAVA_SRC environment variable when source collection is enabled");
@@ -16,6 +16,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
@@ -23,6 +25,7 @@ import com.microsoft.playwright.options.HttpHeader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
@@ -30,6 +33,8 @@ import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
class Utils {
static <F, T> T convertType(F f, Class<T> t) {
if (f == null) {
@@ -144,6 +149,49 @@ class Utils {
return mimeType;
}
static final int maxUplodBufferSize = 50 * 1024 * 1024;
static boolean hasLargeFile(Path[] files) {
for (Path file: files) {
try {
if (Files.size(file)> maxUplodBufferSize) {
return true;
}
} catch (IOException e) {
throw new PlaywrightException("Cannot get file size.", e);
}
}
return false;
}
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (context.browser().isRemote) {
List<WritableStream> streams = new ArrayList<>();
JsonArray jsonStreams = new JsonArray();
for (Path path : files) {
WritableStream temp = context.createTempFile(path.getFileName().toString());
streams.add(temp);
try (OutputStream out = temp.stream()) {
Files.copy(path, out);
} catch (IOException e) {
throw new PlaywrightException("Failed to copy file to remote server.", e);
}
jsonStreams.add(temp.toProtocol());
}
params.add("streams", jsonStreams);
} else {
params.add("localPaths", toJsonArray(files));
}
}
static void checkFilePayloadSize(FilePayload[] files) {
for (FilePayload file: files) {
if (file.buffer.length > maxUplodBufferSize) {
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
}
}
}
static FilePayload[] toFilePayloads(Path[] files) {
List<FilePayload> payloads = new ArrayList<>();
for (Path file : files) {
@@ -0,0 +1,39 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
class WritableStream extends ChannelOwner {
WritableStream(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
OutputStream stream() {
return new OutputStream() {
@Override
public void write(int b) throws IOException {
write(new byte[] { (byte) b });
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
JsonObject params = new JsonObject();
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
ByteBuffer encoded = Base64.getEncoder().encode(buffer);
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
sendMessage("write", params);
}
};
}
JsonObject toProtocol() {
JsonObject json = new JsonObject();
json.addProperty("guid", guid);
return json;
}
}
@@ -17,5 +17,6 @@
package com.microsoft.playwright.options;
public enum ScreenshotAnimations {
DISABLED
DISABLED,
ALLOW
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ScreenshotCaret {
HIDE,
INITIAL
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ScreenshotScale {
CSS,
DEVICE
}
@@ -0,0 +1,66 @@
package com.microsoft.playwright;
import com.sun.net.httpserver.HttpExchange;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MultipartFormData {
static MultipartFormData parseRequest(HttpExchange exchange) throws IOException {
ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
try (OutputStream output = bodyBytes) {
Utils.copy(exchange.getRequestBody(), output);
}
String body = new String(bodyBytes.toByteArray(), StandardCharsets.UTF_8);
String contentType = exchange.getRequestHeaders().get("content-type").get(0);
Matcher matcher = Pattern.compile("boundary=(.*)$").matcher(contentType);
if (!matcher.find()) {
throw new RuntimeException("Boundary not found!");
}
String boundary = matcher.group(1);
return new MultipartFormData(body, boundary);
}
static class Field {
final String filename;
final String content;
Field(String filename, String content) {
this.filename = filename;
this.content = content;
}
}
final List<Field> fields = new ArrayList<>();
MultipartFormData(String body, String boundary) {
String[] parts = Pattern.compile("--" + boundary + "(--)?\r\n", Pattern.MULTILINE).split(body);
for (String part : parts) {
if (part.trim().length() == 0) {
continue;
}
String[] headersAndContent = Pattern.compile("\r\n\r\n", Pattern.MULTILINE).split(part);
if (headersAndContent.length != 2) {
throw new RuntimeException("Unexpected format: " + part);
}
String headers = headersAndContent[0];
String filename = null;
for (String header: Pattern.compile("\r\n", Pattern.MULTILINE).split(headers)) {
Matcher matcher = Pattern.compile("content-disposition: .*filename=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE).matcher(header);
if (!matcher.find()) {
continue;
}
filename = matcher.group(1);
}
String content = headersAndContent[1];
content = content.substring(0, content.length() - "\r\n".length());
fields.add(new Field(filename, content));
}
}
}
@@ -30,6 +30,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.*;
@@ -518,4 +520,44 @@ public class TestBrowserTypeConnect extends TestBase {
assertEquals(200, response.status());
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
page.navigate(server.PREFIX + "/input/fileupload.html");
Path uploadFile = tmpDir.resolve("200MB.zip");
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
for (int i = 0; i < 50 * 1024; i++) {
stream.write(str);
}
}
Locator input = page.locator("input[type='file']");
JSHandle events = input.evaluateHandle("e => {\n" +
" const events = [];\n" +
" e.addEventListener('input', () => events.push('input'));\n" +
" e.addEventListener('change', () => events.push('change'));\n" +
" return events;\n" +
" }");
input.setInputFiles(uploadFile);
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
assertEquals(asList("input", "change"), events.evaluate("e => e"));
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
server.setRoute("/upload", exchange -> {
try {
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
formData.complete(multipartFormData);
} catch (Exception e) {
e.printStackTrace();
formData.completeExceptionally(e);
}
exchange.sendResponseHeaders(200, -1);
});
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
List<MultipartFormData.Field> fields = formData.get().fields;
assertEquals(1, fields.size());
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
}
@@ -118,6 +118,13 @@ public class TestLocatorAssertions extends TestBase {
}
}
@Test
void hasTextWTextInnerTextPass() {
page.setContent("<div id=node>Text <span hidden>garbage</span> content</div>");
Locator locator = page.locator("#node");
assertThat(locator).hasText("Text content", new LocatorAssertions.HasTextOptions().setUseInnerText(true));
}
@Test
void hasTextWTextArrayPass() {
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
@@ -577,6 +584,13 @@ public class TestLocatorAssertions extends TestBase {
}
}
@Test
void isCheckedFalsePass() {
page.setContent("<input type=checkbox></input>");
Locator locator = page.locator("input");
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setChecked(false));
}
@Test
void isDisabledPass() {
page.setContent("<button disabled>Text</button>");
@@ -345,31 +345,13 @@ public class TestPageEvaluate extends TestBase {
@Test
void shouldReturnUndefinedForNonSerializableObjects() {
assertEquals(null, page.evaluate("() => window"));
}
@Test
void shouldFailForCircularObject() {
Object result = page.evaluate("() => {\n" +
" const a = {};\n" +
" const b = { a };\n" +
" a.b = b;\n" +
" return a;\n" +
"}");
assertNull(result);
assertEquals(null, page.evaluate("() => () => {}"));
assertEquals("ref: <Window>", page.evaluate("() => window"));
}
@Test
void shouldBeAbleToThrowATrickyError() {
JSHandle windowHandle = page.evaluateHandle("() => window");
String errorText = null;
try {
windowHandle.jsonValue();
fail("did not throw");
} catch (PlaywrightException e) {
errorText = e.getMessage();
}
assertNotNull(errorText);
String errorText = "My error";
try {
page.evaluate("errorText => {\n" +
" throw new Error(errorText);\n" +
@@ -619,4 +601,23 @@ public class TestPageEvaluate extends TestBase {
JSHandle resultHandle = page.evaluateHandle("() => ({ toJSON: () => 'string', data: 'data' })");
assertEquals(mapOf("data", "data", "toJSON", emptyMap()), resultHandle.jsonValue());
}
@Test
void shouldAliasWindowDocumentAndNode() {
Object object = page.evaluate("[window, document, document.body]");
assertEquals(asList("ref: <Window>", "ref: <Document>", "ref: <Node>"), object);
}
@Test
void shouldWorkForCircularObject() {
Object result = page.evaluate("() => {\n" +
" const a = {};\n" +
" a.b = a;\n" +
" return a;\n" +
" }");
Map<String, Object> map = (Map<String, Object>) result;
assertEquals(1, map.size());
assertTrue(map == map.get("b"));
}
}
@@ -227,5 +227,15 @@ public class TestPageExposeFunction extends TestBase {
assertTrue(e.getMessage().contains("exposeBindingHandle supports a single argument, 2 received"));
}
}
@Test
void shouldSerializeCycles() {
Object[] object = { null };
page.exposeBinding("log", (source, obj) -> object[0] = obj[0]);
page.evaluate("const a = {}; a.b = a; window.log(a)");
Map<String, Object> map = (Map<String, Object>) object[0];
assertEquals(1, map.size());
assertTrue(map == map.get("b"));
}
}
@@ -134,4 +134,19 @@ public class TestPageLocatorQuery extends TestBase {
assertThat(page.locator("div", new Page.LocatorOptions()
.setHas(page.locator("span")).setHasText("wor"))).hasCount(1);
}
@Test
void shouldSupportLocatorFilter() {
page.setContent("<section><div><span>hello</span></div><div><span>world</span></div></section>");
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHasText("hello"))).hasCount(1);
assertThat(page.locator("div", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("hello"))).hasCount(1);
assertThat(page.locator("div", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("world"))).hasCount(0);
assertThat(page.locator("section", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("world"))).hasCount(1);
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHasText("hello")).locator("span")).hasCount(1);
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHas(page.locator("span", new Page.LocatorOptions().setHasText("world"))))).hasCount(1);
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHas(page.locator("span")))).hasCount(2);
assertThat(page.locator("div").filter(new Locator.FilterOptions()
.setHas(page.locator("span"))
.setHasText("world"))).hasCount(1);
}
}
@@ -256,7 +256,7 @@ public class TestPageRoute extends TestBase {
}
assertNotNull(failedRequest[0]);
if (isWebKit()) {
assertEquals("Request intercepted", failedRequest[0].failure());
assertEquals("Blocked by Web Inspector", failedRequest[0].failure());
} else if (isFirefox()) {
assertEquals("NS_ERROR_OFFLINE", failedRequest[0].failure());
} else {
@@ -281,7 +281,7 @@ public class TestPageRoute extends TestBase {
fail("did not throw");
} catch (PlaywrightException e) {
if (isWebKit())
assertTrue(e.getMessage().contains("Request intercepted"));
assertTrue(e.getMessage().contains("Blocked by Web Inspector"), e.getMessage());
else if (isFirefox())
assertTrue(e.getMessage().contains("NS_ERROR_FAILURE"));
else
@@ -534,9 +534,7 @@ public class TestPageRoute extends TestBase {
page.navigate(server.EMPTY_PAGE);
page.route("**/cars*", route -> {
Map<String, String> headers = new HashMap<>();
if (route.request().url().endsWith("allow")) {
headers.put("access-control-allow-origin", "*");
}
headers.put("access-control-allow-origin", route.request().url().endsWith("allow") ? "*" : "none");
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("application/json")
@@ -700,4 +698,29 @@ public class TestPageRoute extends TestBase {
page.navigate(server.EMPTY_PAGE);
assertEquals(1, intercepted[0]);
}
@Test
void shouldAddAccessControlAllowOriginByDefaultWhenFulfill() {
page.navigate(server.EMPTY_PAGE);
page.route("**/cars", route -> {
route.fulfill(new Route.FulfillOptions()
.setContentType("application/json")
.setStatus(200)
.setBody("[\"electric\",\"gas\"]"));
});
Response response = page.waitForResponse("https://example.com/cars", () -> {
Object result = page.evaluate("async () => {\n" +
" const response = await fetch('https://example.com/cars', {\n" +
" method: 'POST',\n" +
" headers: { 'Content-Type': 'application/json' },\n" +
" mode: 'cors',\n" +
" body: JSON.stringify({ 'number': 1 })\n" +
" });\n" +
" return response.text();\n" +
" }");
assertEquals("[\"electric\",\"gas\"]", result);
});
assertEquals(server.PREFIX, response.headerValue("Access-Control-Allow-Origin"));
}
}
@@ -18,6 +18,8 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.Clip;
import com.microsoft.playwright.options.ScreenshotAnimations;
import com.microsoft.playwright.options.ScreenshotCaret;
import com.microsoft.playwright.options.ScreenshotScale;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
@@ -27,6 +29,7 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import static com.microsoft.playwright.options.ScreenshotAnimations.DISABLED;
import static java.util.Arrays.asList;
@@ -151,6 +154,96 @@ public class TestPageScreenshot extends TestBase {
return;
}
fail("Screenshots are equal");
}
@Test
void shouldWorkWithDeviceScaleFactorAndClip() {
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setViewportSize(500, 500).setDeviceScaleFactor(3))) {
Page page = context.newPage();
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setClip(50, 100, 150, 100));
assertNotNull(screenshot);
// TODO:
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-clip.png");
}
}
@Test
void shouldWorkWithDeviceScaleFactorAndScaleCss() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setViewportSize(320, 480).setDeviceScaleFactor(2));
Page page = context.newPage();
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setScale(ScreenshotScale.CSS));
assertNotNull(screenshot);
// TODO:
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-css-size.png");
}
@Test
void shouldWorkWithDeviceScaleFactorAndScaleDevice() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setViewportSize(320, 480).setDeviceScaleFactor(2));
Page page = context.newPage();
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setScale(ScreenshotScale.DEVICE));
assertNotNull(screenshot);
// TODO:
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-device-size.png");
}
@Test
void shouldNotCaptureBlinkingCaretByDefault() {
page.setContent("<!-- Refer to stylesheet from other origin. Accessing this\n" +
" stylesheet rules will throw.\n" +
" -->\n" +
" <link rel=stylesheet href=\"" + server.CROSS_PROCESS_PREFIX + "/injectedstyle.css\">\n" +
" <!-- make life harder: define caret color in stylesheet -->\n" +
" <style>\n" +
" div {\n" +
" caret-color: #000 !important;\n" +
" }\n" +
" </style>\n" +
" <div contenteditable=\"true\"></div>\n");
Locator div = page.locator("div");
div.type("foo bar");
byte[] screenshot = div.screenshot();
for (int i = 0; i < 10; ++i) {
// Caret blinking time is set to 500ms.
// Try to capture variety of screenshots to make
// sure we don"t capture blinking caret.
page.waitForTimeout(150);
byte[] newScreenshot = div.screenshot();
assertArrayEquals(screenshot, newScreenshot);
}
}
@Test
void shouldCaptureBlinkingCaretIfExplicitlyAskedFor() {
page.setContent(" <!-- Refer to stylesheet from other origin. Accessing this\n" +
" stylesheet rules will throw.\n" +
" -->\n" +
" <link rel=stylesheet href=\"" + server.CROSS_PROCESS_PREFIX + "/injectedstyle.css'}\">\n" +
" <!-- make life harder: define caret color in stylesheet -->\n" +
" <style>\n" +
" div {\n" +
" caret-color: #000 !important;\n" +
" }\n" +
" </style>\n" +
" <div contenteditable=\"true\"></div>\n");
Locator div = page.locator("div");
div.type("foo bar");
byte[] screenshot = div.screenshot();
boolean hasDifferentScreenshots = false;
for (int i = 0; !hasDifferentScreenshots && i < 10; ++i) {
// Caret blinking time is set to 500ms.
// Try to capture variety of screenshots to make
// sure we capture blinking caret.
page.waitForTimeout(150);
byte[] newScreenshot = div.screenshot(new Locator.ScreenshotOptions().setCaret(ScreenshotCaret.INITIAL));
hasDifferentScreenshots = !Arrays.equals(newScreenshot, screenshot);
}
assertTrue(hasDifferentScreenshots);
}
}
@@ -17,15 +17,22 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.FilePayload;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import static java.util.Arrays.asList;
@@ -49,6 +56,46 @@ public class TestPageSetInputFiles extends TestBase {
"}", input));
}
@Test
void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
page.navigate(server.PREFIX + "/input/fileupload.html");
Path uploadFile = tmpDir.resolve("200MB.zip");
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
for (int i = 0; i < 50 * 1024; i++) {
stream.write(str);
}
}
Locator input = page.locator("input[type='file']");
JSHandle events = input.evaluateHandle("e => {\n" +
" const events = [];\n" +
" e.addEventListener('input', () => events.push('input'));\n" +
" e.addEventListener('change', () => events.push('change'));\n" +
" return events;\n" +
" }");
input.setInputFiles(uploadFile);
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
assertEquals(asList("input", "change"), events.evaluate("e => e"));
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
server.setRoute("/upload", exchange -> {
try {
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
formData.complete(multipartFormData);
} catch (Exception e) {
e.printStackTrace();
formData.completeExceptionally(e);
}
exchange.sendResponseHeaders(200, -1);
});
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
List<MultipartFormData.Field> fields = formData.get().fields;
assertEquals(1, fields.size());
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
@Test
void shouldWork() {
page.setContent("<input type=file>");
@@ -41,7 +41,8 @@ public class TestPlaywrightCreate {
getBrowserTypeFromEnv(playwright).launch();
fail("Did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Looks like Playwright Test or Playwright was just installed or updated"), e.getMessage());
assertTrue(e.getMessage().contains("Looks like Playwright Test or Playwright was just installed or updated") ||
e.getMessage().contains("Looks like Playwright was just installed or updated."), e.getMessage());
}
try (DirectoryStream<Path> ds = Files.newDirectoryStream(browsersDir)) {
@@ -0,0 +1,221 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestSelectorsMisc extends TestBase {
@Test
void shouldWorkWithLayoutSelectors() {
/*
+--+ +--+
| 1| | 2|
+--+ ++-++
| 3| | 4|
+-------+ ++-++
| 0 | | 5|
| +--+ +--+--+
| | 6| | 7|
| +--+ +--+
| |
O-------+
+--+
| 8|
+--++--+
| 9|
+--+
*/
Object[][] boxes = {
// x, y, width, height
{0, 0, 150, 150},
{100, 200, 50, 50},
{200, 200, 50, 50},
{100, 150, 50, 50},
{201, 150, 50, 50},
{200, 100, 50, 50},
{50, 50, 50, 50},
{150, 50, 50, 50},
{150, -51, 50, 50},
{201, -101, 50, 50},
};
page.setContent("<container style='width: 500px; height: 500px; position: relative;'></container>");
page.evalOnSelector("container", "(container, boxes) => {\n" +
" for (let i = 0; i < boxes.length; i++) {\n" +
" const div = document.createElement('div');\n" +
" div.style.position = 'absolute';\n" +
" div.style.overflow = 'hidden';\n" +
" div.style.boxSizing = 'border-box';\n" +
" div.style.border = '1px solid black';\n" +
" div.id = 'id' + i;\n" +
" div.textContent = 'id' + i;\n" +
" const box = boxes[i];\n" +
" div.style.left = box[0] + 'px';\n" +
" // Note that top is a flipped y coordinate.\n" +
" div.style.top = (250 - box[1] - box[3]) + 'px';\n" +
" div.style.width = box[2] + 'px';\n" +
" div.style.height = box[3] + 'px';\n" +
" container.appendChild(div);\n" +
" const span = document.createElement('span');\n" +
" span.textContent = '' + i;\n" +
" div.appendChild(span);\n" +
" }\n" +
" }", boxes);
assertEquals("id7", page.evalOnSelector("div:right-of(#id6)", "e => e.id"));
// assertEquals("id7", page.evalOnSelector("div >> right-of=\"#id6\"", "e => e.id"));
// assertEquals("id7", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id6")))
// .first().evaluate("e => e.id"));
assertEquals("id2", page.evalOnSelector("div:right-of(#id1)", "e => e.id"));
// assertEquals("id2", page.evalOnSelector("div >> right-of=\"#id1\"", "e => e.id"));
assertEquals("id4", page.evalOnSelector("div:right-of(#id3)", "e => e.id"));
// assertEquals("id4", page.evalOnSelector("div >> right-of=\"#id3\"", "e => e.id"));
assertNull(page.querySelector("div:right-of(#id4)"));
// assertNull(page.querySelector("div >> right-of=\"#id4\""));
assertEquals("id7", page.evalOnSelector("div:right-of(#id0)", "e => e.id"));
// assertEquals("id7", page.evalOnSelector("div >> right-of=\"#id0\"", "e => e.id"));
assertEquals("id9", page.evalOnSelector("div:right-of(#id8)", "e => e.id"));
// assertEquals("id9", page.evalOnSelector("div >> right-of=\"#id8\"", "e => e.id"));
assertEquals("id4,id2,id5,id7,id8,id9", page.evalOnSelectorAll("div:right-of(#id3)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id4,id2,id5,id7,id8,id9", page.evalOnSelectorAll("div >> right-of=\"#id3\"", "els => els.map(e => e.id).join(',')"));
// assertEquals("4,2,5,7,8,9", page.locator("div",
// new Page.LocatorOptions().setRightOf(page.locator("#id3"))).locator("span")
// .evaluateAll("els => els.map(e => e.textContent).join(',')"));
assertEquals("id2,id5,id7,id8", page.evalOnSelectorAll("div:right-of(#id3, 50)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id2,id5,id7,id8", page.evalOnSelectorAll("div >> right-of=\"#id3\",50", "els => els.map(e => e.id).join(',')"));
// assertEquals("2,5,7,8", page.evalOnSelectorAll("div >> right-of=\"#id3\",50 >> span", "els => els.map(e => e.textContent).join(',')"));
// assertEquals("4,2,5,7,8,9", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id3")))
// .locator("span").evaluateAll("els => els.map(e => e.textContent).join(',')"));
assertEquals("id7,id8", page.evalOnSelectorAll("div:right-of(#id3, 49)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id7,id8", page.evalOnSelectorAll("div >> right-of=\"#id3\",49", "els => els.map(e => e.id).join(',')"));
// assertEquals("7,8", page.evalOnSelectorAll("div >> right-of=\"#id3\",49 >> span", "els => els.map(e => e.textContent).join(',')"));
// assertEquals("4,2,5,7,8,9", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id3")))
// .locator("span").evaluateAll("els => els.map(e => e.textContent).join(',')"));
assertEquals("id1", page.evalOnSelector("div:left-of(#id2)", "e => e.id"));
// assertEquals("id1", page.evalOnSelector("div >> left-of=\"#id2\"", "e => e.id"));
// assertEquals("id1", page.locator("div", new Page.LocatorOptions().setLeftOf(page.locator("#id2"))).first().evaluate("e => e.id"));
assertNull(page.querySelector("div:left-of(#id0)"));
// assertNull(page.querySelector("div >> left-of=\"#id0\""));
assertEquals("id0", page.evalOnSelector("div:left-of(#id5)", "e => e.id"));
// assertEquals("id0", page.evalOnSelector("div >> left-of=\"#id5\"", "e => e.id"));
assertEquals("id8", page.evalOnSelector("div:left-of(#id9)", "e => e.id"));
// assertEquals("id8", page.evalOnSelector("div >> left-of=\"#id9\"", "e => e.id"));
assertEquals("id3", page.evalOnSelector("div:left-of(#id4)", "e => e.id"));
// assertEquals("id3", page.evalOnSelector("div >> left-of=\"#id4\"", "e => e.id"));
assertEquals("id0,id7,id3,id1,id6,id8", page.evalOnSelectorAll("div:left-of(#id5)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0,id7,id3,id1,id6,id8", page.evalOnSelectorAll("div >> left-of=\"#id5\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id7,id8", page.evalOnSelectorAll("div:left-of(#id5, 3)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id7,id8", page.evalOnSelectorAll("div >> left-of=\"#id5\",3", "els => els.map(e => e.id).join(',')"));
// assertEquals("7,8", page.evalOnSelectorAll("div >> left-of=\"#id5\",3 >> span", "els => els.map(e => e.textContent).join(',')"));
assertEquals("id3", page.evalOnSelector("div:above(#id0)", "e => e.id"));
// assertEquals("id3", page.evalOnSelector("div >> above=\"#id0\"", "e => e.id"));
// assertEquals("id3", page.locator("div", new Page.LocatorOptions().setAbove(page.locator("#id0")))
// .first().evaluate("e => e.id"));
assertEquals("id4", page.evalOnSelector("div:above(#id5)", "e => e.id"));
// assertEquals("id4", page.evalOnSelector("div >> above=\"#id5\"", "e => e.id"));
assertEquals("id5", page.evalOnSelector("div:above(#id7)", "e => e.id"));
// assertEquals("id5", page.evalOnSelector("div >> above=\"#id7\"", "e => e.id"));
assertEquals("id0", page.evalOnSelector("div:above(#id8)", "e => e.id"));
// assertEquals("id0", page.evalOnSelector("div >> above=\"#id8\"", "e => e.id"));
assertEquals("id8", page.evalOnSelector("div:above(#id9)", "e => e.id"));
// assertEquals("id8", page.evalOnSelector("div >> above=\"#id9\"", "e => e.id"));
assertNull(page.querySelector("div:above(#id2)"));
// assertNull(page.querySelector("div >> above=\"#id2\""));
assertEquals("id4,id2,id3,id1", page.evalOnSelectorAll("div:above(#id5)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id4,id2,id3,id1", page.evalOnSelectorAll("div >> above=\"#id5\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id4,id3", page.evalOnSelectorAll("div:above(#id5, 20)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id4,id3", page.evalOnSelectorAll("div >> above=\"#id5\",20", "els => els.map(e => e.id).join(',')"));
assertEquals("id5", page.evalOnSelector("div:below(#id4)", "e => e.id"));
// assertEquals("id5", page.evalOnSelector("div >> below=\"#id4\"", "e => e.id"));
// assertEquals("id5", page.locator("div", new Page.LocatorOptions().setBelow(page.locator("#id4")))
// .first().evaluate("e => e.id"));
assertEquals("id0", page.evalOnSelector("div:below(#id3)", "e => e.id"));
// assertEquals("id0", page.evalOnSelector("div >> below=\"#id3\"", "e => e.id"));
assertEquals("id4", page.evalOnSelector("div:below(#id2)", "e => e.id"));
// assertEquals("id4", page.evalOnSelector("div >> below=\"#id2\"", "e => e.id"));
assertEquals("id8", page.evalOnSelector("div:below(#id6)", "e => e.id"));
// assertEquals("id8", page.evalOnSelector("div >> below=\"#id6\"", "e => e.id"));
assertEquals("id8", page.evalOnSelector("div:below(#id7)", "e => e.id"));
// assertEquals("id8", page.evalOnSelector("div >> below=\"#id7\"", "e => e.id"));
assertEquals("id9", page.evalOnSelector("div:below(#id8)", "e => e.id"));
// assertEquals("id9", page.evalOnSelector("div >> below=\"#id8\"", "e => e.id"));
assertNull(page.querySelector("div:below(#id9)"));
// assertNull(page.querySelector("div >> below=\"#id9\""));
assertEquals("id0,id5,id6,id7,id8,id9", page.evalOnSelectorAll("div:below(#id3)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0,id5,id6,id7,id8,id9", page.evalOnSelectorAll("div >> below=\"#id3\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id0,id5,id6,id7", page.evalOnSelectorAll("div:below(#id3, 105)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0,id5,id6,id7", page.evalOnSelectorAll("div >> below=\"#id3\" , 105", "els => els.map(e => e.id).join(',')"));
assertEquals("id3", page.evalOnSelector("div:near(#id0)", "e => e.id"));
// assertEquals("id3", page.evalOnSelector("div >> near=\"#id0\"", "e => e.id"));
// assertEquals("id3", page.locator("div", new Page.LocatorOptions().setNear(page.locator("#id0")))
// .first().evaluate("e => e.id"));
assertEquals("id0,id5,id3,id6", page.evalOnSelectorAll("div:near(#id7)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0,id5,id3,id6", page.evalOnSelectorAll("div >> near=\"#id7\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id3,id6,id7,id8,id1,id5", page.evalOnSelectorAll("div:near(#id0)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id3,id6,id7,id8,id1,id5", page.evalOnSelectorAll("div >> near=\"#id0\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id0,id3,id7", page.evalOnSelectorAll("div:near(#id6)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0,id3,id7", page.evalOnSelectorAll("div >> near=\"#id6\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id0", page.evalOnSelectorAll("div:near(#id6, 10)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id0", page.evalOnSelectorAll("div >> near=\"#id6\",10", "els => els.map(e => e.id).join(',')"));
assertEquals("id3,id6,id7,id8,id1,id5,id4,id2", page.evalOnSelectorAll("div:near(#id0, 100)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id3,id6,id7,id8,id1,id5,id4,id2", page.evalOnSelectorAll("div >> near=\"#id0\",100", "els => els.map(e => e.id).join(',')"));
assertEquals("id7,id6", page.evalOnSelectorAll("div:below(#id5):above(#id8)", "els => els.map(e => e.id).join(',')"));
// assertEquals("id7,id6", page.evalOnSelectorAll("div >> below=\"#id5\" >> above=\"#id8\"", "els => els.map(e => e.id).join(',')"));
assertEquals("id7", page.evalOnSelector("div:below(#id5):above(#id8)", "e => e.id"));
// assertEquals("id7", page.evalOnSelector("div >> below=\"#id5\" >> above=\"#id8\"", "e => e.id"));
// assertEquals("id7", page.locator("div", new Page.LocatorOptions()
// .setBelow(page.locator("#id5"))
// .setAbove(page.locator("#id8"))).first().evaluate("e => e.id"));
assertEquals("id5,id6,id3", page.evalOnSelectorAll("div:right-of(#id0) + div:above(#id8)", "els => els.map(e => e.id).join(',')"));
try {
ElementHandle error = page.querySelector(":near(50)");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"near\" engine expects a selector list and optional maximum distance in pixels"), e.getMessage());
}
try {
ElementHandle error1 = page.querySelector("div >> left-of=abc");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed selector: left-of=abc"));
}
try {
ElementHandle error2 = page.querySelector("left-of=\"div\"");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"left-of\" selector cannot be first"), e.getMessage());
}
try {
ElementHandle error3 = page.querySelector("div >> left-of=33");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed selector: left-of=33"));
}
try {
ElementHandle error4 = page.querySelector("div >> left-of='span','foo'");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed selector: left-of='span','foo'"));
}
try {
ElementHandle error5 = page.querySelector("div >> left-of='span',3,4");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Malformed selector: left-of='span',3,4"));
}
}
}
@@ -43,7 +43,6 @@ public class TestTracing extends TestBase {
@BeforeAll
void launchBrowser(@TempDir Path tempDir) {
System.out.println("new launchBrowser(");
BrowserType.LaunchOptions options = createLaunchOptions();
options.setTracesDir(tempDir.resolve("trace-dir"));
launchBrowser(options);
@@ -126,4 +125,10 @@ public class TestTracing extends TestBase {
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
}
@Test
void shouldNotFailWhenSourcesSetExplicitlyToFalse() throws IOException {
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") == null, "PLAYWRIGHT_JAVA_SRC must not be set for this test");
context.tracing().start(new Tracing.StartOptions().setSources(false));
}
}
@@ -24,6 +24,24 @@ import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestWheel extends TestBase {
private void expectEvent(Map<String, Object> expected) {
// Chromium reports deltaX/deltaY scaled by host device scale factor.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1324819
// https://github.com/microsoft/playwright/issues/7362
// Different bots have different scale factors (usually 1 or 2), so we just ignore the values
// instead of guessing the host scale factor.
// This first appeared in Chromium 102.
boolean ignoreDelta = isChromium() && isMac;
Map<String, Object> received = (Map<String, Object>) page.evaluate("window.lastEvent");
if (ignoreDelta) {
expected.remove("deltaX");
expected.remove("deltaY");
received.remove("deltaX");
received.remove("deltaY");
}
assertEquals(expected, received);
}
@Test
void shouldDispatchWheelEvents() {
page.setContent("<div style='width: 5000px; height: 5000px;'></div>");
@@ -42,7 +60,7 @@ public class TestWheel extends TestBase {
"altKey", false,
"metaKey", false);
page.waitForFunction("window.scrollY === 100");
assertEquals(expected, page.evaluate("window.lastEvent"));
expectEvent(expected);
}
@Test
@@ -71,7 +89,7 @@ public class TestWheel extends TestBase {
"shiftKey", true,
"altKey", false,
"metaKey", false);
assertEquals(expected, page.evaluate("window.lastEvent"));
expectEvent(expected);
}
@Test
@@ -90,7 +108,7 @@ public class TestWheel extends TestBase {
"shiftKey", false,
"altKey", false,
"metaKey", false);
assertEquals(expected, page.evaluate("window.lastEvent"));
expectEvent(expected);
page.waitForFunction("window.scrollX === 100");
}
@@ -113,7 +131,7 @@ public class TestWheel extends TestBase {
"shiftKey", false,
"altKey", false,
"metaKey", false);
assertEquals(expected, page.evaluate("window.lastEvent"));
expectEvent(expected);
// give the page a chacne to scroll
page.waitForTimeout(100);
// ensure that it did not.
@@ -4,8 +4,8 @@
<title>File upload test</title>
</head>
<body>
<form action="/input/fileupload.html">
<input type="file">
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="submit">
</form>
</body>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
+1 -1
View File
@@ -1 +1 @@
1.20.0-beta-1646855573000
1.22.0
+1 -1
View File
@@ -33,7 +33,7 @@ fi
mkdir -p driver
cd driver
for PLATFORM in mac linux win32_x64
for PLATFORM in mac linux linux-arm64 win32_x64
do
FILE_NAME=$FILE_PREFIX-$PLATFORM.zip
if [[ -d $PLATFORM ]]; then
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -181,10 +181,15 @@ abstract class Element {
int start = 0;
while (matcher.find()) {
String url = matcher.group(2);
if (url.startsWith("./")) {
// ./actionability.md#editable => https://playwright.dev/java/docs/actionability/#editable
if (url.startsWith("../")) {
// ../actionability.md#editable => https://playwright.dev/java/docs/actionability/#editable
url = url.replace(".md", "");
url = url.replace("./", "https://playwright.dev/java/docs/");
url = url.replace("../", "https://playwright.dev/java/docs/");
}
if (url.startsWith("./")) {
// ./class-tracing.md => https://playwright.dev/java/docs/api/class-tracing
url = url.replace(".md", "");
url = url.replace("./", "https://playwright.dev/java/docs/api/");
}
String link = "<a href=\"" + url + "\">" + matcher.group(1) + "</a>";
linkified += paragraph.substring(start, matcher.start());
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.19.0-SNAPSHOT</version>
<version>1.22.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
+6 -1
View File
@@ -1,5 +1,7 @@
FROM ubuntu:focal
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-focal"
# === INSTALL JDK and Maven ===
RUN apt-get update && \
@@ -13,7 +15,8 @@ RUN apt-get update && \
# Create the pwuser
adduser pwuser
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
ARG PW_TARGET_ARCH
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-${PW_TARGET_ARCH}
# === BAKE BROWSERS INTO IMAGE ===
@@ -35,5 +38,7 @@ RUN cd /tmp/pw-java && \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
+3 -1
View File
@@ -32,4 +32,6 @@ else
exit 1
fi
docker build --platform "${PLATFORM}" -t "$3" -f "Dockerfile.$2" ../../
PW_TARGET_ARCH=$(echo $1 | cut -c3-)
docker build --platform "${PLATFORM}" --build-arg "PW_TARGET_ARCH=${PW_TARGET_ARCH}" -t "$3" -f "Dockerfile.$2" ../../
+10 -6
View File
@@ -7,14 +7,17 @@ trap "cd $(pwd -P)" EXIT
cd "$(dirname "$0")"
MCR_IMAGE_NAME="playwright/java"
# GITHUB_REF has a form of `refs/tags/v1.3.0`.
# TAG_NAME would be `v1.3.0`
TAG_NAME=${GITHUB_REF#refs/tags/}
PW_VERSION="${TAG_NAME#v}"
POM_FILE=../../pom.xml
if [[ ! -f ${POM_FILE} ]]; then
echo "ERROR: pom.xml not found at ${POM_FILE}"
exit 1;
fi
PW_VERSION=$(mvn exec:exec -Dexec.executable='echo' -Dexec.args='${project.version}' -f ${POM_FILE} --non-recursive -q 2>/dev/null)
RELEASE_CHANNEL="$1"
if [[ "${RELEASE_CHANNEL}" == "stable" ]]; then
if [[ ! "${PW_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
if [[ ! "${PW_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "ERROR: cannot publish stable docker with Playwright version '${PW_VERSION}'"
exit 1
fi
@@ -114,4 +117,5 @@ publish_docker_manifest () {
}
publish_docker_images_with_arch_suffix focal amd64
publish_docker_manifest focal amd64
publish_docker_images_with_arch_suffix focal arm64
publish_docker_manifest focal amd64 arm64