1
0
mirror of synced 2026-05-26 20:53:32 +00:00

Compare commits

...

53 Commits

Author SHA1 Message Date
Andrey Lushnikov 1cc79c4c41 chore: mark v1.15.2 (#638) 2021-10-05 11:52:54 -07:00
Andrey Lushnikov 43da8b23e1 chore: roll to 1.15.2-1633455481000 (#637) 2021-10-05 11:30:26 -07:00
Andrey Lushnikov bcc240d5cb chore: mark v1.15.1 (#634) 2021-09-30 14:21:38 -07:00
Yury Semikhatsky 79668f877f chore(release-1.15): roll driver to 1.15.0-1633020276000 (#633) 2021-09-30 11:42:04 -07:00
Andrey Lushnikov e5e9a7db99 chore: roll Java to the new 1.15.1 driver (#625) 2021-09-23 16:53:09 +02:00
Max Schmitt 0c8706f1eb chore: mark 1.15.0 (#619) 2021-09-21 15:12:25 +02:00
Yury Semikhatsky b000a8b6df chore: simplify issue templates, align with upstream (#611) 2021-09-17 16:20:30 -07:00
Yury Semikhatsky 16cc466622 feat: roll driver, headerValue(s), wheel (#609) 2021-09-17 16:20:14 -07:00
codeboyzhou 958813201a docs: update maven version number for README.md (#605) 2021-09-17 09:03:05 -07:00
Yury Semikhatsky bc7a59d852 chore: more code reuse, enable 2 tests (#599) 2021-09-10 08:08:37 -07:00
Max Schmitt a073eb07ae docs(contributing): add note how to execute tests (#600) 2021-09-10 08:00:24 -07:00
Yury Semikhatsky 2dbc6194a3 feat: catch up with recent feature development upstream (#598) 2021-09-09 18:12:07 -07:00
Yury Semikhatsky 1d69d924cf fix: respect predicate in waitFor* methods (#596) 2021-09-07 12:59:28 -07:00
Max Schmitt a171c39601 feat(roll): roll Playwright to 1.15.0-next-1629487941000 (#583) 2021-08-24 18:26:01 +02:00
Yury Semikhatsky 46baa46e36 docs: update rolling instructions 2021-08-20 08:31:05 -07:00
Yury Semikhatsky cba51c5e96 docs: duplicate field docs to builder methods (#578) 2021-08-20 08:29:46 -07:00
Andrey Lushnikov c17d5d8a9a docs: add rolling.md (#580)
This docs describes how to roll to the Playwright driver.
2021-08-20 05:49:54 -07:00
Yury Semikhatsky 89894e15d2 docs: fix @link reference for method with alias (#577) 2021-08-19 14:52:35 -07:00
Yury Semikhatsky a012836779 chore: update driver, support context-level strict (#575) 2021-08-18 15:08:08 -07:00
Yury Semikhatsky 29c0df6443 devops: fix publish workflow (#565) 2021-08-13 16:47:14 -07:00
Yury Semikhatsky 33ec902eb9 chore: update current version to 1.15.0-SNAPSHOT (#563) 2021-08-13 14:38:07 -07:00
Yury Semikhatsky 104be4728f chore: update driver to 1.14.0-1628878084000 (#561) 2021-08-13 12:32:58 -07:00
Yury Semikhatsky 08776552da test: navigate to download url (#558) 2021-08-13 09:05:40 -07:00
Yury Semikhatsky 70b9e2e034 fix: print warning when skipping browser download (#557) 2021-08-13 09:04:17 -07:00
Yury Semikhatsky ebf9b09c34 devops: check that browser versions in README are up to date (#553) 2021-08-12 08:43:51 -07:00
Yury Semikhatsky cd3b45acd0 chore: update driver to 1.14.0-next-1628705690000 (#552) 2021-08-11 16:49:18 -07:00
Yury Semikhatsky 528d01b07a fix: do not download browsers if PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 (#551) 2021-08-11 15:38:20 -07:00
Yury Semikhatsky ab2efd4d80 test: referer option and more navigation tests (#545) 2021-08-10 23:07:05 -07:00
Yury Semikhatsky 1eb3f3bb80 devops: publish snapshots automatically on each commit (#543) 2021-08-10 14:41:07 -07:00
Yury Semikhatsky fcac298050 feat: roll driver, add dnd tests (#540) 2021-08-06 16:06:52 -07:00
Yury Semikhatsky 476e222c93 fix: support 0 size read from stream (#539) 2021-08-06 12:35:20 -07:00
Yury Semikhatsky 696610de15 fix: event info for waitForLoadState (#534) 2021-07-30 04:54:42 -07:00
Yury Semikhatsky 3e7f8017e0 fix: set apiName for method calls in metadata (#533) 2021-07-29 08:19:35 -07:00
Yury Semikhatsky 3631357a35 feat: roll driver, implement locator (#532) 2021-07-28 09:24:31 -07:00
Yury Semikhatsky 79529a7239 chore: bump snapshot version to 1.14 (#531) 2021-07-28 00:57:59 -07:00
Max Rydahl Andersen 04419c7e5f feat: provide jbang-catalog to run playwright cli (#519)
Co-authored-by: Max Rydahl Andersen <gitkraken@xam.dk>
2021-07-27 00:51:12 -07:00
Yury Semikhatsky fb508701ef docs: update browser versions in readme (#529) 2021-07-26 09:46:32 -07:00
Max Schmitt 654a4dc12f feat(roll): roll Playwright to 1.13.0-1626733671000 (#521) 2021-07-20 14:14:54 +02:00
Max Schmitt 5eecbb5252 fix(websockets): filter for text and binary frames (#522) 2021-07-20 14:07:31 +02:00
Yury Semikhatsky 1b58892e58 fix: reverse route handlers order (#517)
This PR mirrors https://github.com/microsoft/playwright/pull/7585

https://github.com/microsoft/playwright/issues/7394
2021-07-16 10:38:27 -07:00
Yury Semikhatsky a1ef49cd03 feat: roll driver, fix validfrom/to type (#516) 2021-07-13 06:44:17 -07:00
Yury Semikhatsky 2b0b50358b feat: remaining baseUrl implementation bits (#515) 2021-07-13 06:15:36 -07:00
Yury Semikhatsky 399de8e899 feat: roll driver to 07/12, implement new features (#514) 2021-07-12 09:35:17 -07:00
Max Schmitt c66b95afb3 chore: use Java code in GitHub issue code snippet placeholder 2021-07-07 12:42:22 +02:00
Max Schmitt 1f0f1b06e1 chore: sync with upstream GitHub issue templates (#511) 2021-07-07 01:58:28 -07:00
Yury Semikhatsky 5a0dd8595c fix: NPE in ElementHandle.hover() (#509) 2021-07-06 03:01:28 -07:00
Yury Semikhatsky 518f117fb0 chore: roll driver to 1.13.0-next-1623789547000 (#493) 2021-06-15 14:29:30 -07:00
Yury Semikhatsky 539b18f167 fix: support tracing over CDP (#492) 2021-06-15 13:55:20 -07:00
Yury Semikhatsky 1958a2fa64 fix: respect WebSocket.waitForFrame* predicate (#490) 2021-06-15 12:25:44 -07:00
Yury Semikhatsky 87ad579deb feat: accept driver env in Playwright.create() (#480) 2021-06-09 16:17:02 -07:00
Yury Semikhatsky e83ef2b1c0 chore: update versions in README (#479) 2021-06-09 09:24:10 -07:00
Yury Semikhatsky f23c5d4c01 chore: update driver to 1.13.0-next-1623183015000 (#474) 2021-06-08 13:37:00 -07:00
Yury Semikhatsky d3f06cefd8 chore: update current version to 1.13.0-SNAPSHOT (#475) 2021-06-08 13:34:38 -07:00
128 changed files with 11421 additions and 729 deletions
+41
View File
@@ -0,0 +1,41 @@
---
name: Bug Report
about: Something doesn't work like it should? Tell us!
title: "[BUG]"
labels: ''
assignees: ''
---
**Context:**
- Playwright Version: [what Playwright version do you use?]
- Operating System: [e.g. Windows, Linux or Mac]
- Browser: [e.g. All, Chromium, Firefox, WebKit]
- Extra: [any specific details about your environment]
<!-- CLI to auto-capture this info -->
<!-- npx envinfo --preset playwright --markdown -->
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: Join our Slack community
url: https://aka.ms/playwright-slack
about: Ask questions and discuss with other community members
+11
View File
@@ -0,0 +1,11 @@
---
name: Feature request
about: Request new features to be added
title: "[Feature]"
labels: ''
assignees: ''
---
Let us know what functionality you'd like to see in Playwright and what your use case is.
Do you think others might benefit from this as well?
+10
View File
@@ -0,0 +1,10 @@
---
name: I have a question
about: Feel free to ask us your questions!
title: "[Question]"
labels: ''
assignees: ''
---
+38
View File
@@ -0,0 +1,38 @@
---
name: Report regression
about: Functionality that used to work and does not any more
title: "[REGRESSION]: "
labels: ''
assignees: ''
---
**Context:**
- GOOD Playwright Version: [what Playwright version worked nicely?]
- BAD Playwright Version: [what Playwright version doesn't work any more?]
- Operating System: [e.g. Windows, Linux or Mac]
- Extra: [any specific details about your environment]
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
+5 -4
View File
@@ -1,15 +1,16 @@
name: Publish
on:
workflow_dispatch
workflow_dispatch:
push:
branches:
- master
jobs:
build:
timeout-minutes: 30
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: microsoft/playwright-github-action@v1.5.0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: microsoft/playwright-github-action@v1.5.0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
@@ -64,7 +64,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: microsoft/playwright-github-action@v1.5.0
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
+4 -1
View File
@@ -22,6 +22,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:
@@ -32,10 +33,12 @@ jobs:
run: scripts/download_driver_for_all_platforms.sh
- name: Regenerate APIs
run: scripts/generate_api.sh
- name: Update browser versions in README
run: scripts/update_readme.sh
- name: Verify API is up to date
run: |
if [[ -n $(git status -s) ]]; then
echo "ERROR: generated interfaces differ from the current sources:"
echo "ERROR: generated interfaces/docs differ from the current sources:"
git diff
exit 1
fi
+4
View File
@@ -33,6 +33,10 @@ Names of published driver archives can be found at https://github.com/microsoft/
```bash
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
```
### Generating API
+4 -4
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->92.0.4498.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->14.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->89.0b6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->96.0.4641.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->92.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
@@ -43,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.11.0</version>
<version>1.14.1</version>
</dependency>
```
+7
View File
@@ -0,0 +1,7 @@
# Rolling Playwright-Java to the latest Playwright driver
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* set new driver version in `scripts/CLI_VERSION`
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.12.0-SNAPSHOT</version>
<version>1.15.2</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -21,25 +21,40 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private final Path driverTempDir;
DriverJar() throws IOException, URISyntaxException, InterruptedException {
driverTempDir = Files.createTempDirectory("playwright-java-");
driverTempDir.toFile().deleteOnExit();
extractDriverToTempDir();
installBrowsers();
}
private void installBrowsers() throws IOException, InterruptedException {
@Override
protected void initialize(Map<String, String> env) throws Exception {
extractDriverToTempDir();
installBrowsers(env);
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
String skip = env.get(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
if (skip == null) {
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
}
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
String cliFileName = super.cliFileName();
Path driver = driverTempDir.resolve(cliFileName);
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
}
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.environment().putAll(env);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -31,7 +32,7 @@ public class TestInstall {
void playwrightCliInstalled() throws Exception {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
Path cli = Driver.ensureDriverInstalled();
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap());
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.12.0-SNAPSHOT</version>
<version>1.15.2</version>
</parent>
<artifactId>driver</artifactId>
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
/**
* This class provides access to playwright-cli. It can be either preinstalled
@@ -32,16 +33,23 @@ public abstract class Driver {
PreinstalledDriver(Path driverDir) {
this.driverDir = driverDir;
}
@Override
protected void initialize(Map<String, String> env) {
// no-op
}
@Override
Path driverDir() {
return driverDir;
}
}
public static synchronized Path ensureDriverInstalled() {
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
if (instance == null) {
try {
instance = createDriver();
instance.initialize(env);
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
@@ -50,6 +58,8 @@ public abstract class Driver {
return instance.driverDir().resolve(name);
}
protected abstract void initialize(Map<String, String> env) throws Exception;
protected String cliFileName() {
return System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright.cmd" : "playwright.sh";
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.12.0-SNAPSHOT</version>
<version>1.15.2</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+10
View File
@@ -0,0 +1,10 @@
{
"catalogs": {},
"aliases": {
"playwright": {
"script-ref": "scripts/playwright.java",
"description": "Playwright lets you automate Chromium, Firefox and Webkit with a single API. \nWith this cli you can install, trace, generate pdf and screenshots and more.\nExample on how to record and run a script:\n```\n jbang playwright@microsoft/playwright-java codegen -o Example.java`\n jbang --deps com.microsoft.playwright:playwright:RELEASE Example.java\n```"
}
},
"templates": {}
}
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.12.0-SNAPSHOT</version>
<version>1.15.2</version>
</parent>
<artifactId>playwright</artifactId>
@@ -60,6 +60,18 @@ public interface Browser extends AutoCloseable {
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
@@ -74,9 +86,17 @@ public interface Browser extends AutoCloseable {
*/
public Double deviceScaleFactor;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
* An object containing additional HTTP headers to be sent with every request.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public ForcedColors forcedColors;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
@@ -87,7 +107,7 @@ public interface Browser extends AutoCloseable {
*/
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
@@ -163,6 +183,12 @@ public interface Browser extends AutoCloseable {
* state.
*/
public Path storageStatePath;
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public Boolean strictSelectors;
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
@@ -178,26 +204,68 @@ public interface Browser extends AutoCloseable {
*/
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public NewContextOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public NewContextOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public NewContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public NewContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public NewContextOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
return this;
}
public NewContextOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
@@ -205,97 +273,207 @@ public interface Browser extends AutoCloseable {
this.geolocation = geolocation;
return this;
}
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public NewContextOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public NewContextOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public NewContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public NewContextOptions setLocale(String locale) {
this.locale = locale;
return this;
}
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public NewContextOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
/**
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public NewContextOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
/**
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public NewContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
/**
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public NewContextOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public NewContextOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public NewContextOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public NewContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public NewContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public NewContextOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public NewContextOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
*/
public NewContextOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public NewContextOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public NewContextOptions setStrictSelectors(boolean strictSelectors) {
this.strictSelectors = strictSelectors;
return this;
}
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
* metaZones.txt</a> for a list of supported timezone IDs.
*/
public NewContextOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
/**
* Specific user agent to use in this context.
*/
public NewContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public NewContextOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public NewContextOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
@@ -306,6 +484,18 @@ public interface Browser extends AutoCloseable {
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
@@ -320,9 +510,17 @@ public interface Browser extends AutoCloseable {
*/
public Double deviceScaleFactor;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
* An object containing additional HTTP headers to be sent with every request.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public ForcedColors forcedColors;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
@@ -333,7 +531,7 @@ public interface Browser extends AutoCloseable {
*/
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
@@ -409,6 +607,12 @@ public interface Browser extends AutoCloseable {
* state.
*/
public Path storageStatePath;
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public Boolean strictSelectors;
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
@@ -424,26 +628,68 @@ public interface Browser extends AutoCloseable {
*/
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public NewPageOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public NewPageOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public NewPageOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public NewPageOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public NewPageOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public NewPageOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
return this;
}
public NewPageOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
@@ -451,97 +697,207 @@ public interface Browser extends AutoCloseable {
this.geolocation = geolocation;
return this;
}
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public NewPageOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewPageOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewPageOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public NewPageOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public NewPageOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public NewPageOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public NewPageOptions setLocale(String locale) {
this.locale = locale;
return this;
}
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public NewPageOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
/**
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public NewPageOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
/**
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public NewPageOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public NewPageOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public NewPageOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
/**
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public NewPageOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public NewPageOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public NewPageOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public NewPageOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public NewPageOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public NewPageOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public NewPageOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
*/
public NewPageOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public NewPageOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public NewPageOptions setStrictSelectors(boolean strictSelectors) {
this.strictSelectors = strictSelectors;
return this;
}
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
* metaZones.txt</a> for a list of supported timezone IDs.
*/
public NewPageOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
/**
* Specific user agent to use in this context.
*/
public NewPageOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public NewPageOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public NewPageOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
@@ -561,14 +917,23 @@ public interface Browser extends AutoCloseable {
*/
public Boolean screenshots;
/**
* specify custom categories to use instead of default.
*/
public StartTracingOptions setCategories(List<String> categories) {
this.categories = categories;
return this;
}
/**
* A path to write the trace file to.
*/
public StartTracingOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* captures screenshots in the trace.
*/
public StartTracingOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
@@ -643,7 +1008,9 @@ public interface Browser extends AutoCloseable {
*/
Page newPage(NewPageOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
* <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>.
*
* <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.
@@ -660,7 +1027,9 @@ public interface Browser extends AutoCloseable {
startTracing(page, null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
* <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>.
*
* <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.
@@ -675,7 +1044,9 @@ public interface Browser extends AutoCloseable {
startTracing(null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
* <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>.
*
* <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.
@@ -690,7 +1061,9 @@ public interface Browser extends AutoCloseable {
*/
void startTracing(Page page, StartTracingOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
* <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>.
*
* <p> Returns the buffer with trace data.
*/
@@ -137,6 +137,10 @@ public interface BrowserContext extends AutoCloseable {
*/
public Boolean handle;
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
* supported. When passing by value, multiple arguments are supported.
*/
public ExposeBindingOptions setHandle(boolean handle) {
this.handle = handle;
return this;
@@ -148,11 +152,28 @@ public interface BrowserContext extends AutoCloseable {
*/
public String origin;
/**
* The [origin] to grant permissions to, e.g. "https://example.com".
*/
public GrantPermissionsOptions setOrigin(String origin) {
this.origin = origin;
return this;
}
}
class RouteOptions {
/**
* How often a route should be used. By default it will be used every time.
*/
public Integer times;
/**
* How often a route should be used. By default it will be used every time.
*/
public RouteOptions setTimes(int times) {
this.times = times;
return this;
}
}
class StorageStateOptions {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
@@ -160,6 +181,10 @@ public interface BrowserContext extends AutoCloseable {
*/
public Path path;
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public StorageStateOptions setPath(Path path) {
this.path = path;
return this;
@@ -176,10 +201,17 @@ public interface BrowserContext extends AutoCloseable {
*/
public Double timeout;
/**
* Receives the {@code Page} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForPageOptions setPredicate(Predicate<Page> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForPageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -519,6 +551,10 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
@@ -555,14 +591,22 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
void route(String url, Consumer<Route> handler);
default void route(String url, Consumer<Route> handler) {
route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
@@ -599,14 +643,20 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
void route(Pattern url, Consumer<Route> handler);
void route(String url, Consumer<Route> handler, RouteOptions options);
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
@@ -643,16 +693,172 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
void route(Predicate<String> url, Consumer<Route> handler);
default void route(Pattern url, Consumer<Route> handler) {
route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> or the same snippet using a regex pattern instead:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
void route(Pattern url, Consumer<Route> handler, RouteOptions options);
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> or the same snippet using a regex pattern instead:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
default void route(Predicate<String> url, Consumer<Route> handler) {
route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
*
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> or the same snippet using a regex pattern instead:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
* Page page = context.newPage();
* page.navigate("https://example.com");
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
* options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
*/
void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
/**
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
* <ul>
* <li> {@link Page#goBack Page.goBack()}</li>
* <li> {@link Page#goForward Page.goForward()}</li>
* <li> {@link Page#goto Page.goto()}</li>
* <li> {@link Page#navigate Page.navigate()}</li>
* <li> {@link Page#reload Page.reload()}</li>
* <li> {@link Page#setContent Page.setContent()}</li>
* <li> {@link Page#waitForNavigation Page.waitForNavigation()}</li>
@@ -57,14 +57,25 @@ public interface BrowserType {
*/
public Double timeout;
/**
* Additional HTTP headers to be sent with web socket connect request. Optional.
*/
public ConnectOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public ConnectOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
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.
*/
public ConnectOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -86,14 +97,25 @@ public interface BrowserType {
*/
public Double timeout;
/**
* Additional HTTP headers to be sent with connect request. Optional.
*/
public ConnectOverCDPOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public ConnectOverCDPOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
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.
*/
public ConnectOverCDPOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -122,7 +144,8 @@ public interface BrowserType {
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed.
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
* is closed.
*/
public Path downloadsPath;
/**
@@ -187,82 +210,159 @@ public interface BrowserType {
*/
public Path tracesDir;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public LaunchOptions setArgs(List<String> args) {
this.args = args;
return this;
}
@Deprecated
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(String channel) {
this.channel = channel;
return this;
}
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public LaunchOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
*/
public LaunchOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
* is closed.
*/
public LaunchOptions setDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
*/
public LaunchOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
* or WebKit, use at your own risk.
*/
public LaunchOptions setExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*/
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
*/
public LaunchOptions setHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
/**
* Close the browser process on Ctrl-C. Defaults to {@code true}.
*/
public LaunchOptions setHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
/**
* Close the browser process on SIGTERM. Defaults to {@code true}.
*/
public LaunchOptions setHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
*/
public LaunchOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
*/
public LaunchOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
/**
* Network proxy settings.
*/
public LaunchOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings.
*/
public LaunchOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
public LaunchOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public LaunchOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* If specified, traces are saved into this directory.
*/
public LaunchOptions setTracesDir(Path tracesDir) {
this.tracesDir = tracesDir;
return this;
@@ -278,6 +378,18 @@ public interface BrowserType {
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public List<String> args;
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
@@ -308,7 +420,8 @@ public interface BrowserType {
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed.
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
* is closed.
*/
public Path downloadsPath;
/**
@@ -322,9 +435,17 @@ public interface BrowserType {
*/
public Path executablePath;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
* An object containing additional HTTP headers to be sent with every request.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public ForcedColors forcedColors;
public Geolocation geolocation;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
@@ -364,7 +485,7 @@ public interface BrowserType {
*/
public List<String> ignoreDefaultArgs;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
@@ -429,6 +550,12 @@ public interface BrowserType {
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
public Double slowMo;
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public Boolean strictSelectors;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
@@ -453,59 +580,135 @@ public interface BrowserType {
*/
public Optional<ViewportSize> viewportSize;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public LaunchPersistentContextOptions setArgs(List<String> args) {
this.args = args;
return this;
}
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* </ul>
*/
public LaunchPersistentContextOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public LaunchPersistentContextOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
@Deprecated
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel;
return this;
}
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public LaunchPersistentContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
*/
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
* is closed.
*/
public LaunchPersistentContextOptions setDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
*/
public LaunchPersistentContextOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
* or WebKit, use at your own risk.
*/
public LaunchPersistentContextOptions setExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public LaunchPersistentContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
*
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
* tracker.
*/
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = forcedColors;
return this;
}
public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
@@ -513,125 +716,251 @@ public interface BrowserType {
this.geolocation = geolocation;
return this;
}
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
/**
* Close the browser process on Ctrl-C. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
/**
* Close the browser process on SIGTERM. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public LaunchPersistentContextOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public LaunchPersistentContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public LaunchPersistentContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
*/
public LaunchPersistentContextOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public LaunchPersistentContextOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public LaunchPersistentContextOptions setLocale(String locale) {
this.locale = locale;
return this;
}
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
/**
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public LaunchPersistentContextOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
/**
* Network proxy settings.
*/
public LaunchPersistentContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings.
*/
public LaunchPersistentContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
/**
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public LaunchPersistentContextOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public LaunchPersistentContextOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public LaunchPersistentContextOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
*/
public LaunchPersistentContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public LaunchPersistentContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public LaunchPersistentContextOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public LaunchPersistentContextOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
public LaunchPersistentContextOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
*/
public LaunchPersistentContextOptions setStrictSelectors(boolean strictSelectors) {
this.strictSelectors = strictSelectors;
return this;
}
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public LaunchPersistentContextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
* metaZones.txt</a> for a list of supported timezone IDs.
*/
public LaunchPersistentContextOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
/**
* If specified, traces are saved into this directory.
*/
public LaunchPersistentContextOptions setTracesDir(Path tracesDir) {
this.tracesDir = tracesDir;
return this;
}
/**
* Specific user agent to use in this context.
*/
public LaunchPersistentContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public LaunchPersistentContextOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public LaunchPersistentContextOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
@@ -20,6 +20,7 @@ import com.microsoft.playwright.impl.Driver;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import static java.util.Arrays.asList;
@@ -28,7 +29,7 @@ import static java.util.Arrays.asList;
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled();
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
ProcessBuilder pb = new ProcessBuilder(driver.toString());
pb.command().addAll(asList(args));
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
@@ -19,11 +19,11 @@ package com.microsoft.playwright;
import java.util.*;
/**
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsole Page.onConsole()} event.
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event.
*/
public interface ConsoleMessage {
/**
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsole Page.onConsole()}.
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsoleMessage Page.onConsoleMessage()}.
*/
List<JSHandle> args();
/**
@@ -23,8 +23,7 @@ import java.util.*;
/**
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
*
* <p> If {@code downloadsPath} isn't specified, all the downloaded files belonging to the browser context are deleted when the
* browser context is closed. And all downloaded files are deleted when the browser closes.
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
@@ -47,6 +46,11 @@ import java.util.*;
* has no access to the downloaded files.
*/
public interface Download {
/**
* Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations,
* {@code download.failure()} would resolve to {@code "canceled"}.
*/
void cancel();
/**
* Returns readable stream for current download or {@code null} if download failed.
*/
@@ -66,6 +70,9 @@ public interface Download {
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
*
* <p> Note that the download's file name is a random GUID, use {@link Download#suggestedFilename Download.suggestedFilename()}
* to get suggested file name.
*/
Path path();
/**
@@ -24,21 +24,8 @@ import java.util.*;
* ElementHandle represents an in-page DOM element. ElementHandles can be created with the {@link Page#querySelector
* Page.querySelector()} method.
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType chromium = playwright.chromium();
* Browser browser = chromium.launch();
* Page page = browser.newPage();
* page.navigate("https://example.com");
* ElementHandle hrefElement = page.querySelector("a");
* hrefElement.click();
* // ...
* }
* }
* }
* ElementHandle hrefElement = page.querySelector("a");
* hrefElement.click();
* }</pre>
*
* <p> ElementHandle prevents DOM element from garbage collection unless the handle is disposed with {@link JSHandle#dispose
@@ -46,6 +33,30 @@ import java.util.*;
*
* <p> ElementHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()} and {@link
* Page#evaluate Page.evaluate()} methods.
*
* <p> <strong>NOTE:</strong> In most cases, you would want to use the {@code Locator} object instead. You should only use {@code ElementHandle} if you want to
* retain a handle to a particular DOM Node that you intend to pass into {@link Page#evaluate Page.evaluate()} as an
* argument.
*
* <p> The difference between the {@code Locator} and ElementHandle is that the ElementHandle points to a particular element, while
* {@code Locator} captures the logic of how to retrieve an element.
*
* <p> In the example below, handle points to a particular DOM element on page. If that element changes text or is used by
* React to render an entirely different component, handle is still pointing to that very DOM element. This can lead to
* unexpected behaviors.
* <pre>{@code
* ElementHandle handle = page.querySelector("text=Submit");
* handle.hover();
* handle.click();
* }</pre>
*
* <p> With the locator, every time the {@code element} is used, up-to-date DOM element is located in the page using the selector. So
* in the snippet below, underlying DOM element is going to be located twice.
* <pre>{@code
* Locator locator = page.locator("text=Submit");
* locator.hover();
* locator.click();
* }</pre>
*/
public interface ElementHandle extends JSHandle {
class CheckOptions {
@@ -78,25 +89,52 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public CheckOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public CheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public CheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public CheckOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public CheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public CheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
@@ -149,41 +187,81 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Defaults to {@code left}.
*/
public ClickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public ClickOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public ClickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public ClickOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public ClickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public ClickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public ClickOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public ClickOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public ClickOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public ClickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
@@ -232,43 +310,85 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Defaults to {@code left}.
*/
public DblclickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public DblclickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public DblclickOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public DblclickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public DblclickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public DblclickOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public DblclickOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public DblclickOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public DblclickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class FillOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public Boolean force;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
@@ -282,10 +402,28 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public FillOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public FillOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public FillOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -320,30 +458,74 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public HoverOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public HoverOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public HoverOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public HoverOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public HoverOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public HoverOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class InputValueOptions {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public InputValueOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
@@ -362,14 +544,27 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public PressOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public PressOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public PressOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -402,22 +597,42 @@ public interface ElementHandle extends JSHandle {
*/
public ScreenshotType type;
/**
* Hides default white background and allows capturing screenshots with transparency. Not applicable to {@code jpeg} images.
* Defaults to {@code false}.
*/
public ScreenshotOptions setOmitBackground(boolean omitBackground) {
this.omitBackground = omitBackground;
return this;
}
/**
* The file path to save the image to. The screenshot type will be inferred from file extension. If {@code path} is a relative
* path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to
* the disk.
*/
public ScreenshotOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
*/
public ScreenshotOptions setQuality(int quality) {
this.quality = quality;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public ScreenshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* Specify screenshot type, defaults to {@code png}.
*/
public ScreenshotOptions setType(ScreenshotType type) {
this.type = type;
return this;
@@ -431,12 +646,22 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public ScrollIntoViewIfNeededOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class SelectOptionOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public Boolean force;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
@@ -450,16 +675,39 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public SelectOptionOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SelectOptionOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class SelectTextOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public Boolean force;
/**
* 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
@@ -467,11 +715,105 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public SelectTextOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SelectTextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class SetCheckedOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public Boolean force;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public SetCheckedOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public SetCheckedOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public SetCheckedOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SetCheckedOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public SetCheckedOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class SetInputFilesOptions {
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
@@ -486,10 +828,20 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetInputFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SetInputFilesOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -530,29 +882,60 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public TapOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public TapOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TapOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public TapOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public TapOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public TapOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public TapOptions setTrial(boolean trial) {
this.trial = trial;
return this;
@@ -576,14 +959,27 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public TypeOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TypeOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public TypeOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -619,25 +1015,52 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean trial;
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
* {@code false}.
*/
public UncheckOptions setForce(boolean force) {
this.force = force;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public UncheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public UncheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public UncheckOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public UncheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public UncheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
@@ -651,6 +1074,11 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public WaitForElementStateOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -669,6 +1097,11 @@ public interface ElementHandle extends JSHandle {
* </ul>
*/
public WaitForSelectorState state;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
* element, the call throws an exception.
*/
public Boolean strict;
/**
* 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
@@ -676,10 +1109,34 @@ public interface ElementHandle extends JSHandle {
*/
public Double timeout;
/**
* Defaults to {@code "visible"}. Can be either:
* <ul>
* <li> {@code "attached"} - wait for element to be present in DOM.</li>
* <li> {@code "detached"} - wait for element to not be present in DOM.</li>
* <li> {@code "visible"} - wait for element to have non-empty bounding box and no {@code visibility:hidden}. Note that element without any
* content or with {@code display:none} has an empty bounding box and is not considered visible.</li>
* <li> {@code "hidden"} - wait for element to be either detached from DOM, or have an empty bounding box or {@code visibility:hidden}. This
* is opposite to the {@code "visible"} option.</li>
* </ul>
*/
public WaitForSelectorOptions setState(WaitForSelectorState state) {
this.state = state;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more then one
* element, the call throws an exception.
*/
public WaitForSelectorOptions setStrict(boolean strict) {
this.strict = strict;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public WaitForSelectorOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -1079,6 +1536,16 @@ public interface ElementHandle extends JSHandle {
* Returns the {@code element.innerText}.
*/
String innerText();
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
*/
default String inputValue() {
return inputValue(null);
}
/**
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
*/
String inputValue(InputValueOptions options);
/**
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
*/
@@ -1549,6 +2016,46 @@ public interface ElementHandle extends JSHandle {
* the element and selects all its text content.
*/
void selectText(SelectTextOptions options);
/**
* This method checks or unchecks an element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws.</li>
* <li> If the element already has the right checked state, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param checked Whether to check or uncheck the checkbox.
*/
default void setChecked(boolean checked) {
setChecked(checked, null);
}
/**
* This method checks or unchecks an element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws.</li>
* <li> If the element already has the right checked state, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param checked Whether to check or uncheck the checkbox.
*/
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>.
@@ -42,10 +42,20 @@ public interface FileChooser {
*/
public Double timeout;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SetFilesOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
File diff suppressed because it is too large Load Diff
@@ -60,6 +60,9 @@ public interface Keyboard {
*/
public Double delay;
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public PressOptions setDelay(double delay) {
this.delay = delay;
return this;
@@ -71,6 +74,9 @@ public interface Keyboard {
*/
public Double delay;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public TypeOptions setDelay(double delay) {
this.delay = delay;
return this;
File diff suppressed because it is too large Load Diff
@@ -49,14 +49,23 @@ public interface Mouse {
*/
public Double delay;
/**
* Defaults to {@code left}.
*/
public ClickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public ClickOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public ClickOptions setDelay(double delay) {
this.delay = delay;
return this;
@@ -72,10 +81,16 @@ public interface Mouse {
*/
public Double delay;
/**
* Defaults to {@code left}.
*/
public DblclickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public DblclickOptions setDelay(double delay) {
this.delay = delay;
return this;
@@ -91,10 +106,16 @@ public interface Mouse {
*/
public Integer clickCount;
/**
* Defaults to {@code left}.
*/
public DownOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public DownOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
@@ -106,6 +127,9 @@ public interface Mouse {
*/
public Integer steps;
/**
* defaults to 1. Sends intermediate {@code mousemove} events.
*/
public MoveOptions setSteps(int steps) {
this.steps = steps;
return this;
@@ -121,10 +145,16 @@ public interface Mouse {
*/
public Integer clickCount;
/**
* Defaults to {@code left}.
*/
public UpOptions setButton(MouseButton button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public UpOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
@@ -182,5 +212,15 @@ public interface Mouse {
* Dispatches a {@code mouseup} event.
*/
void up(UpOptions options);
/**
* Dispatches a {@code wheel} event.
*
* <p> <strong>NOTE:</strong> Wheel events may cause scrolling if they are not handled, and this method does not wait for the scrolling to finish
* before returning.
*
* @param deltaX Pixels to scroll horizontally.
* @param deltaY Pixels to scroll vertically.
*/
void wheel(double deltaX, double deltaY);
}
File diff suppressed because it is too large Load Diff
@@ -40,6 +40,22 @@ import java.util.*;
* }</pre>
*/
public interface Playwright extends AutoCloseable {
class CreateOptions {
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public Map<String, String> env;
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public CreateOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
}
/**
* This object can be used to launch or connect to Chromium, returning instances of {@code Browser}.
*/
@@ -72,8 +88,12 @@ public interface Playwright extends AutoCloseable {
* playwright.close();
* }</pre>
*/
static Playwright create(CreateOptions options) {
return PlaywrightImpl.create(options);
}
static Playwright create() {
return PlaywrightImpl.create();
return create(null);
}
}
@@ -38,6 +38,10 @@ import java.util.*;
* request is issued to a redirected url.
*/
public interface Request {
/**
* An object with all the request HTTP headers associated with this request. The header names are lower-cased.
*/
Map<String, String> allHeaders();
/**
* The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event.
*
@@ -54,9 +58,22 @@ public interface Request {
*/
Frame frame();
/**
* An object with HTTP headers associated with the request. All header names are lower-case.
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
* Request.allHeaders()} instead.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this request. Unlike {@link Request#allHeaders
* Request.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive.
*
* @param name Name of the header.
*/
String headerValue(String name);
/**
* Whether this request is driving frame's navigation.
*/
@@ -112,6 +129,10 @@ public interface Request {
* Returns the matching {@code Response} object, or {@code null} if the response was not received due to error.
*/
Response response();
/**
* Returns resource size information for given request.
*/
Sizes sizes();
/**
* Returns resource timing information for given request. Most of the timing values become available upon the response,
* {@code responseEnd} becomes available when request finishes. Find more information at <a
@@ -16,18 +16,23 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@code Response} class represents responses which are received by page.
*/
public interface Response {
/**
* An object with all the response HTTP headers associated with this response.
*/
Map<String, String> allHeaders();
/**
* Returns the buffer with response body.
*/
byte[] body();
/**
* Waits for this response to finish, returns failure error if request failed.
* Waits for this response to finish, returns always {@code null}.
*/
String finished();
/**
@@ -35,9 +40,30 @@ public interface Response {
*/
Frame frame();
/**
* Returns the object with HTTP headers associated with the response. All header names are lower-case.
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
* Response.allHeaders()} instead.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Unlike {@link Response#allHeaders
* Response.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n} separator is used. If
* no headers are found, {@code null} is returned.
*
* @param name Name of the header.
*/
String headerValue(String name);
/**
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case insensitive.
*
* @param name Name of the header.
*/
List<String> headerValues(String name);
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*/
@@ -46,6 +72,14 @@ public interface Response {
* Returns the matching {@code Request} object.
*/
Request request();
/**
* Returns SSL and other security information.
*/
SecurityDetails securityDetails();
/**
* Returns the IP address and port of the server.
*/
ServerAddr serverAddr();
/**
* Contains the status code of the response (e.g., 200 for a success).
*/
@@ -43,22 +43,37 @@ public interface Route {
*/
public String url;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public ResumeOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* If set changes the request method (e.g. GET or POST)
*/
public ResumeOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request
*/
public ResumeOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request
*/
public ResumeOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public ResumeOptions setUrl(String url) {
this.url = url;
return this;
@@ -91,26 +106,45 @@ public interface Route {
*/
public Integer status;
/**
* Optional response body as text.
*/
public FulfillOptions setBody(String body) {
this.body = body;
return this;
}
/**
* Optional response body as raw bytes.
*/
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
this.bodyBytes = bodyBytes;
return this;
}
/**
* If set, equals to setting {@code Content-Type} response header.
*/
public FulfillOptions setContentType(String contentType) {
this.contentType = contentType;
return this;
}
/**
* Response headers. Header values will be converted to a string.
*/
public FulfillOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
* is resolved relative to the current working directory.
*/
public FulfillOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* Response status code, defaults to {@code 200}.
*/
public FulfillOptions setStatus(int status) {
this.status = status;
return this;
@@ -190,7 +224,7 @@ public interface Route {
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*/
default void fulfill() {
@@ -212,7 +246,7 @@ public interface Route {
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*/
void fulfill(FulfillOptions options);
@@ -32,6 +32,11 @@ public interface Selectors {
*/
public Boolean contentScript;
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
* guaranteed when this engine is used together with other registered engines.
*/
public RegisterOptions setContentScript(boolean contentScript) {
this.contentScript = contentScript;
return this;
@@ -20,19 +20,19 @@ import java.nio.file.Path;
import java.util.*;
/**
* API for collecting and saving Playwright traces. Playwright traces can be opened using the Playwright CLI after
* Playwright script runs.
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
* href="https://playwright.dev/java/docs/trace-viewer/">Trace Viewer</a> after Playwright script runs.
*
* <p> Start with specifying the folder traces will be stored in:
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code
* Browser browser = chromium.launch();
* BrowserContext context = browser.newContext();
* context.tracing.start(new Tracing.StartOptions()
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* .setSnapshots(true));
* Page page = context.newPage();
* page.goto("https://playwright.dev");
* context.tracing.stop(new Tracing.StopOptions()
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
@@ -52,14 +52,24 @@ public interface Tracing {
*/
public Boolean snapshots;
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder specified
* in {@link BrowserType#launch BrowserType.launch()}.
*/
public StartOptions setName(String name) {
this.name = name;
return this;
}
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public StartOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
/**
* Whether to capture DOM snapshot on every action.
*/
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
@@ -67,24 +77,43 @@ public interface Tracing {
}
class StopOptions {
/**
* Export trace into the file with the given name.
* Export trace into the file with the given path.
*/
public Path path;
/**
* Export trace into the file with the given path.
*/
public StopOptions setPath(Path path) {
this.path = path;
return this;
}
}
class StopChunkOptions {
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
*/
public Path path;
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
*/
public StopChunkOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(new Tracing.StartOptions()
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* .setSnapshots(true));
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop(new Tracing.StopOptions()
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
@@ -94,16 +123,41 @@ public interface Tracing {
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(new Tracing.StartOptions()
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* .setSnapshots(true));
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop(new Tracing.StopOptions()
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
void start(StartOptions options);
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.click("text=Get Started");
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
void startChunk();
/**
* Stop tracing.
*/
@@ -114,5 +168,15 @@ public interface Tracing {
* Stop tracing.
*/
void stop(StopOptions options);
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
*/
default void stopChunk() {
stopChunk(null);
}
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
*/
void stopChunk(StopChunkOptions options);
}
@@ -72,10 +72,17 @@ public interface WebSocket {
*/
public Double timeout;
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameReceivedOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -92,10 +99,17 @@ public interface WebSocket {
*/
public Double timeout;
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameSentOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -52,6 +52,10 @@ public interface Worker {
*/
public Double timeout;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForCloseOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
@@ -40,6 +40,10 @@ class ArtifactImpl extends ChannelOwner {
return stream.stream();
}
public void cancel() {
sendMessage("cancel");
}
public void delete() {
sendMessage("delete");
}
@@ -26,6 +26,8 @@ import com.microsoft.playwright.options.FunctionCallback;
import com.microsoft.playwright.options.Geolocation;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -53,6 +55,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
Path recordHarPath;
enum EventType {
CLOSE,
@@ -73,6 +77,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
this.tracing = new TracingImpl(this);
}
void setBaseUrl(String spec) {
try {
this.baseUrl = new URL(spec);
} catch (MalformedURLException e) {
this.baseUrl = null;
}
}
@Override
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -133,9 +145,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.RESPONSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(timeout));
return runUntil(code, new WaitableRace<>(waitables));
@@ -150,7 +162,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (options == null) {
options = new WaitForPageOptions();
}
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
return waitForEventWithTimeout(EventType.PAGE, code, options.predicate, options.timeout);
}
@Override
@@ -169,6 +181,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
isClosedOrClosing = true;
try {
if (recordHarPath != null) {
JsonObject json = sendMessage("harExport").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (browser() != null && browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(recordHarPath);
artifact.delete();
}
sendMessage("close");
} catch (PlaywrightException e) {
if (!isSafeCloseError(e)) {
@@ -306,23 +330,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void route(String url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(this.baseUrl, url), handler, options);
}
@Override
public void route(Pattern url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
@Override
public void route(Predicate<String> url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
private void route(UrlMatcher matcher, Consumer<Route> handler) {
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler);
routes.add(matcher, handler, options == null ? null : options.times);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
@@ -406,7 +430,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(url), handler);
unroute(new UrlMatcher(this.baseUrl, url), handler);
}
@Override
@@ -477,6 +501,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
@@ -491,6 +516,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
@@ -165,12 +165,13 @@ class BrowserImpl extends ChannelOwner implements Browser {
params.addProperty("noDefaultViewport", true);
}
}
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
if (options.recordVideoDir != null) {
context.videosDir = options.recordVideoDir;
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
contexts.add(context);
return context;
}
@@ -75,7 +75,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), headers, timeout, slowMo);
Connection connection = new Connection(transport);
PlaywrightImpl playwright = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
connection.close();
@@ -119,7 +119,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("sdkLanguage", "java");
params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
@@ -183,12 +182,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
params.addProperty("noDefaultViewport", true);
}
}
params.addProperty("sdkLanguage", "java");
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
if (options.recordVideoDir != null) {
context.videosDir = options.recordVideoDir;
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.recordHarPath = options.recordHarPath;
return context;
}
@@ -69,9 +69,18 @@ class ChannelOwner extends LoggingSupport {
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
return super.withLogging(apiName, new WaitForEventLogger<>(this, apiName, code));
return new WaitForEventLogger<>(this, apiName, code).get();
}
@Override
<T> T withLogging(String apiName, Supplier<T> code) {
String previousApiName = connection.setApiName(apiName);
try {
return super.withLogging(apiName, code);
} finally {
connection.setApiName(previousApiName);
}
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
return connection.sendMessageAsync(guid, method, params);
@@ -19,16 +19,12 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -68,6 +64,7 @@ public class Connection {
private int lastId = 0;
private final Path srcDir;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName;
private static final boolean isLogging;
static {
String debug = System.getenv("DEBUG");
@@ -76,7 +73,14 @@ public class Connection {
class Root extends ChannelOwner {
Root(Connection connection) {
super(connection, "", "");
super(connection, "Root", "");
}
Playwright initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
}
}
@@ -94,6 +98,12 @@ public class Connection {
}
}
String setApiName(String name) {
String previous = apiName;
apiName = name;
return previous;
}
void close() throws IOException {
transport.close();
}
@@ -154,11 +164,14 @@ public class Connection {
message.addProperty("guid", guid);
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
if (srcDir != null) {
JsonObject metadata = new JsonObject();
metadata.add("stack", currentStackTrace());
message.add("metadata", metadata);
}
if (apiName != null) {
metadata.addProperty("apiName", apiName);
}
message.add("metadata", metadata);
String messageString = gson().toJson(message);
if (isLogging) {
logWithTimestamp("SEND ► " + messageString);
@@ -167,11 +180,8 @@ public class Connection {
return result;
}
public ChannelOwner waitForObjectWithKnownName(String guid) {
while (!objects.containsKey(guid)) {
processOneMessage();
}
return objects.get(guid);
public PlaywrightImpl initializePlaywright() {
return (PlaywrightImpl) this.root.initialize();
}
public <T> T getExistingObject(String guid) {
@@ -295,6 +305,10 @@ public class Connection {
case "ElementHandle":
result = new ElementHandleImpl(parent, type, guid, initializer);
break;
case "FetchRequest":
// Create fake object as this API is experimental an only exposed in Node.js.
result = new ChannelOwner(parent, type, guid, initializer);
break;
case "Frame":
result = new FrameImpl(parent, type, guid, initializer);
break;
@@ -23,7 +23,7 @@ import com.microsoft.playwright.Page;
import java.io.InputStream;
import java.nio.file.Path;
class DownloadImpl extends LoggingSupport implements Download {
class DownloadImpl implements Download {
private final PageImpl page;
private final ArtifactImpl artifact;
private final JsonObject initializer;
@@ -44,19 +44,24 @@ class DownloadImpl extends LoggingSupport implements Download {
return initializer.get("suggestedFilename").getAsString();
}
@Override
public void cancel() {
page.withLogging("Download.cancel", () -> artifact.cancel());
}
@Override
public InputStream createReadStream() {
return withLogging("Download.createReadStream", () -> artifact.createReadStream());
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
}
@Override
public void delete() {
withLogging("Download.delete", () -> artifact.delete());
page.withLogging("Download.delete", () -> artifact.delete());
}
@Override
public String failure() {
return withLogging("Download.failure", () -> artifact.failure());
return page.withLogging("Download.failure", () -> artifact.failure());
}
@Override
@@ -66,11 +71,11 @@ class DownloadImpl extends LoggingSupport implements Download {
@Override
public Path path() {
return withLogging("Download.path", () -> artifact.pathAfterFinished());
return page.withLogging("Download.path", () -> artifact.pathAfterFinished());
}
@Override
public void saveAs(Path path) {
withLogging("Download.saveAs", () -> artifact.saveAs(path));
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
}
}
@@ -34,6 +34,7 @@ import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -209,10 +210,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void hover(HoverOptions options) {
withLogging("ElementHandle.hover", () -> {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("hover", params);
});
withLogging("ElementHandle.hover", () -> hoverImpl(options));
}
private void hoverImpl(HoverOptions options) {
if (options == null) {
options = new HoverOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("hover", params);
}
@Override
@@ -231,6 +237,20 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
});
}
@Override
public String inputValue(InputValueOptions options) {
return withLogging("ElementHandle.inputValue", () -> inputValueImpl(options));
}
private String inputValueImpl(InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked() {
return withLogging("ElementHandle.isChecked", () -> {
@@ -412,6 +432,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
}
@Override
public void setChecked(boolean checked, SetCheckedOptions options) {
if (checked) {
check(convertViaJson(options, CheckOptions.class));
} else {
uncheck(convertViaJson(options, UncheckOptions.class));
}
}
@Override
public void setInputFiles(Path files, SetInputFilesOptions options) {
setInputFiles(new Path[]{files}, options);
@@ -41,6 +41,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
FrameImpl parentFrame;
Set<FrameImpl> childFrames = new LinkedHashSet<>();
private final Set<LoadState> loadStates = new HashSet<>();
enum InternalEventType { NAVIGATED, LOADSTATE }
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
PageImpl page;
@@ -70,12 +71,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public ElementHandle querySelector(String selector) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector));
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
}
ElementHandleImpl querySelectorImpl(String selector) {
JsonObject params = new JsonObject();
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
if (options == null) {
options = new QuerySelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
@@ -133,12 +137,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg));
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options));
}
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
if (options == null) {
options = new EvalOnSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
@@ -407,6 +414,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
sendMessage("hover", params);
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options));
}
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
if (options == null) {
options = new DragAndDropOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source);
params.addProperty("target", target);
sendMessage("dragAndDrop", params);
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
@@ -437,6 +459,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
return json.get("value").getAsString();
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options));
}
String inputValueImpl(String selector, InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
@@ -522,6 +559,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
}
@Override
public Locator locator(String selector) {
return new LocatorImpl(this, selector);
}
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
if (options == null) {
options = new IsVisibleOptions();
@@ -601,6 +643,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
return parseStringList(json.getAsJsonArray("values"));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options));
}
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
checkImpl(selector, convertViaJson(options, CheckOptions.class));
} else {
uncheckImpl(selector, convertViaJson(options, UncheckOptions.class));
}
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Frame.setContent", () -> setContentImpl(html, options));
@@ -739,7 +794,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withLogging("Frame.waitForLoadState", () -> waitForLoadStateImpl(state, options));
withWaitLogging("Frame.waitForLoadState", () -> {
waitForLoadStateImpl(state, options);
return null;
});
}
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
@@ -875,7 +933,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) {
matcher = UrlMatcher.forOneOf(options.url);
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
waitables.add(page.createWaitForCloseHelper());
@@ -920,7 +978,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
}
@Override
@@ -21,7 +21,7 @@ import com.microsoft.playwright.Keyboard;
import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl extends LoggingSupport implements Keyboard {
class KeyboardImpl implements Keyboard {
private final ChannelOwner page;
KeyboardImpl(ChannelOwner page) {
@@ -30,7 +30,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
@Override
public void down(String key) {
withLogging("Keyboard.down", () -> {
page.withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
@@ -39,7 +39,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
@Override
public void insertText(String text) {
withLogging("Keyboard.insertText", () -> {
page.withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
@@ -48,7 +48,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
@Override
public void press(String key, PressOptions options) {
withLogging("Keyboard.press", () -> pressImpl(key, options));
page.withLogging("Keyboard.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
@@ -62,7 +62,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
@Override
public void type(String text, TypeOptions options) {
withLogging("Keyboard.type", () -> typeImpl(text, options));
page.withLogging("Keyboard.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
@@ -76,7 +76,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
@Override
public void up(String key) {
withLogging("Keyboard.up", () -> {
page.withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
@@ -0,0 +1,406 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import com.microsoft.playwright.options.WaitForSelectorState;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiFunction;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
public LocatorImpl(FrameImpl frame, String selector) {
this.frame = frame;
this.selector = selector;
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertViaJson(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
// timeout = handleOptions.timeout;
// }
// timeout = frame.page.timeoutSettings.timeout(timeout);
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
ElementHandle handle = elementHandle(handleOptions);
try {
return callback.apply(handle, options);
} finally {
if (handle != null) {
handle.dispose();
}
}
}
@Override
public List<String> allInnerTexts() {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.innerText)");
}
@Override
public List<String> allTextContents() {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.textContent || '')");
}
@Override
public BoundingBox boundingBox(BoundingBoxOptions options) {
return withElement((h, o) -> h.boundingBox(), options);
}
@Override
public void check(CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
frame.check(selector, convertViaJson(options, Frame.CheckOptions.class).setStrict(true));
}
@Override
public void click(ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
frame.click(selector, convertViaJson(options, Frame.ClickOptions.class).setStrict(true));
}
@Override
public int count() {
return ((Number) evaluateAll("ee => ee.length")).intValue();
}
@Override
public void dblclick(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
frame.dblclick(selector, convertViaJson(options, Frame.DblclickOptions.class).setStrict(true));
}
@Override
public void dispatchEvent(String type, Object eventInit, DispatchEventOptions options) {
if (options == null) {
options = new DispatchEventOptions();
}
frame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class).setStrict(true));
}
@Override
public ElementHandle elementHandle(ElementHandleOptions options) {
if (options == null) {
options = new ElementHandleOptions();
}
Frame.WaitForSelectorOptions frameOptions = convertViaJson(options, Frame.WaitForSelectorOptions.class);
frameOptions.setStrict(true);
frameOptions.setState(WaitForSelectorState.ATTACHED);
return frame.waitForSelector(selector, frameOptions);
}
@Override
public List<ElementHandle> elementHandles() {
return frame.querySelectorAll(selector);
}
@Override
public Object evaluate(String expression, Object arg, EvaluateOptions options) {
return withElement((h, o) -> h.evaluate(expression, arg), options);
}
@Override
public Object evaluateAll(String expression, Object arg) {
return frame.evalOnSelectorAll(selector, expression, arg);
}
@Override
public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
return withElement((h, o) -> h.evaluateHandle(expression, arg), options);
}
@Override
public void fill(String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
frame.fill(selector, value, convertViaJson(options, Frame.FillOptions.class).setStrict(true));
}
@Override
public Locator first() {
return new LocatorImpl(frame, selector + " >> nth=0");
}
@Override
public void focus(FocusOptions options) {
if (options == null) {
options = new FocusOptions();
}
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
}
@Override
public String getAttribute(String name, GetAttributeOptions options) {
if (options == null) {
options = new GetAttributeOptions();
}
return frame.getAttribute(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class).setStrict(true));
}
@Override
public void hover(HoverOptions options) {
if (options == null) {
options = new HoverOptions();
}
frame.hover(selector, convertViaJson(options, Frame.HoverOptions.class).setStrict(true));
}
@Override
public String innerHTML(InnerHTMLOptions options) {
if (options == null) {
options = new InnerHTMLOptions();
}
return frame.innerHTML(selector, convertViaJson(options, Frame.InnerHTMLOptions.class).setStrict(true));
}
@Override
public String innerText(InnerTextOptions options) {
if (options == null) {
options = new InnerTextOptions();
}
return frame.innerText(selector, convertViaJson(options, Frame.InnerTextOptions.class).setStrict(true));
}
@Override
public String inputValue(InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
return frame.inputValue(selector, convertViaJson(options, Frame.InputValueOptions.class).setStrict(true));
}
@Override
public boolean isChecked(IsCheckedOptions options) {
if (options == null) {
options = new IsCheckedOptions();
}
return frame.isChecked(selector, convertViaJson(options, Frame.IsCheckedOptions.class).setStrict(true));
}
@Override
public boolean isDisabled(IsDisabledOptions options) {
if (options == null) {
options = new IsDisabledOptions();
}
return frame.isDisabled(selector, convertViaJson(options, Frame.IsDisabledOptions.class).setStrict(true));
}
@Override
public boolean isEditable(IsEditableOptions options) {
if (options == null) {
options = new IsEditableOptions();
}
return frame.isEditable(selector, convertViaJson(options, Frame.IsEditableOptions.class).setStrict(true));
}
@Override
public boolean isEnabled(IsEnabledOptions options) {
if (options == null) {
options = new IsEnabledOptions();
}
return frame.isEnabled(selector, convertViaJson(options, Frame.IsEnabledOptions.class).setStrict(true));
}
@Override
public boolean isHidden(IsHiddenOptions options) {
if (options == null) {
options = new IsHiddenOptions();
}
return frame.isHidden(selector, convertViaJson(options, Frame.IsHiddenOptions.class).setStrict(true));
}
@Override
public boolean isVisible(IsVisibleOptions options) {
if (options == null) {
options = new IsVisibleOptions();
}
return frame.isVisible(selector, convertViaJson(options, Frame.IsVisibleOptions.class).setStrict(true));
}
@Override
public Locator last() {
return new LocatorImpl(frame, selector + " >> nth=-1");
}
@Override
public Locator locator(String selector) {
return new LocatorImpl(frame, this.selector + " >> " + selector);
}
@Override
public Locator nth(int index) {
return new LocatorImpl(frame, selector + " >> nth=" + index);
}
@Override
public void press(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
frame.press(selector, key, convertViaJson(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertViaJson(options, ElementHandle.ScreenshotOptions.class));
}
@Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o);
return null;
}, convertViaJson(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}
@Override
public List<String> selectOption(String values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public List<String> selectOption(ElementHandle values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public List<String> selectOption(String[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public List<String> selectOption(SelectOption values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public List<String> selectOption(ElementHandle[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
}
@Override
public void selectText(SelectTextOptions options) {
withElement((h, o) -> {
h.selectText(o);
return null;
}, convertViaJson(options, ElementHandle.SelectTextOptions.class));
}
@Override
public void setChecked(boolean checked, SetCheckedOptions options) {
if (options == null) {
options = new SetCheckedOptions();
}
frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class).setStrict(true));
}
@Override
public void setInputFiles(Path files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
}
@Override
public void tap(TapOptions options) {
if (options == null) {
options = new TapOptions();
}
frame.tap(selector, convertViaJson(options, Frame.TapOptions.class).setStrict(true));
}
@Override
public String textContent(TextContentOptions options) {
if (options == null) {
options = new TextContentOptions();
}
return frame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class).setStrict(true));
}
@Override
public void type(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
frame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class).setStrict(true));
}
@Override
public void uncheck(UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
frame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class).setStrict(true));
}
@Override
public String toString() {
return "Locator@" + selector;
}
}
@@ -93,6 +93,16 @@ class MouseImpl implements Mouse {
page.withLogging("Mouse.up", () -> upImpl(options));
}
@Override
public void wheel(double deltaX, double deltaY) {
page.withLogging("Mouse.wheel", () -> {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params);
});
}
private void upImpl(UpOptions options) {
if (options == null) {
options = new UpOptions();
@@ -125,7 +125,14 @@ public class PageImpl extends ChannelOwner implements Page {
if (listeners.hasListeners(EventType.DIALOG)) {
listeners.notify(EventType.DIALOG, dialog);
} else {
dialog.dismiss();
if ("beforeunload".equals(dialog.type())) {
try {
dialog.accept();
} catch (PlaywrightException e) {
}
} else {
dialog.dismiss();
}
}
} else if ("worker".equals(event)) {
String guid = params.getAsJsonObject("worker").get("guid").getAsString();
@@ -445,7 +452,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForCloseOptions();
}
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
return waitForEventWithTimeout(EventType.CLOSE, code, null, options.timeout);
}
@Override
@@ -457,7 +464,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.timeout);
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
}
@Override
@@ -469,7 +476,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForDownloadOptions();
}
return waitForEventWithTimeout(EventType.DOWNLOAD, code, options.timeout);
return waitForEventWithTimeout(EventType.DOWNLOAD, code, options.predicate, options.timeout);
}
@Override
@@ -482,7 +489,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForFileChooserOptions();
}
return waitForEventWithTimeout(EventType.FILECHOOSER, code, options.timeout);
return waitForEventWithTimeout(EventType.FILECHOOSER, code, options.predicate, options.timeout);
}
@Override
@@ -494,7 +501,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForPopupOptions();
}
return waitForEventWithTimeout(EventType.POPUP, code, options.timeout);
return waitForEventWithTimeout(EventType.POPUP, code, options.predicate, options.timeout);
}
@Override
@@ -506,7 +513,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForWebSocketOptions();
}
return waitForEventWithTimeout(EventType.WEBSOCKET, code, options.timeout);
return waitForEventWithTimeout(EventType.WEBSOCKET, code, options.predicate, options.timeout);
}
@Override
@@ -518,12 +525,12 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForWorkerOptions();
}
return waitForEventWithTimeout(EventType.WORKER, code, options.timeout);
return waitForEventWithTimeout(EventType.WORKER, code, options.predicate, options.timeout);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
@@ -548,8 +555,9 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public ElementHandle querySelector(String selector) {
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(selector));
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
selector, convertViaJson(options, Frame.QuerySelectorOptions.class)));
}
@Override
@@ -558,8 +566,9 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(selector, pageFunction, arg));
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
selector, pageFunction, arg, convertViaJson(options, Frame.EvalOnSelectorOptions.class)));
}
@Override
@@ -716,7 +725,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Frame frameByUrl(String glob) {
return frameFor(new UrlMatcher(glob));
return frameFor(new UrlMatcher(browserContext.baseUrl, glob));
}
@Override
@@ -795,6 +804,12 @@ public class PageImpl extends ChannelOwner implements Page {
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Page.dragAndDrop", () ->
mainFrame.dragAndDropImpl(source, target, convertViaJson(options, Frame.DragAndDropOptions.class)));
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Page.innerHTML",
@@ -807,6 +822,12 @@ public class PageImpl extends ChannelOwner implements Page {
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Page.inputValue",
() -> mainFrame.inputValueImpl(selector, convertViaJson(options, Frame.InputValueOptions.class)));
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked",
@@ -853,6 +874,11 @@ public class PageImpl extends ChannelOwner implements Page {
return keyboard;
}
@Override
public Locator locator(String selector) {
return mainFrame.locator(selector);
}
@Override
public Frame mainFrame() {
return mainFrame;
@@ -924,23 +950,23 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void route(String url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(browserContext.baseUrl, url), handler, options);
}
@Override
public void route(Pattern url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
@Override
public void route(Predicate<String> url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
private void route(UrlMatcher matcher, Consumer<Route> handler) {
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("Page.route", () -> {
routes.add(matcher, handler);
routes.add(matcher, handler, options == null ? null : options.times);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
@@ -1023,6 +1049,12 @@ public class PageImpl extends ChannelOwner implements Page {
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Page.setChecked",
() -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class)));
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Page.setContent",
@@ -1133,7 +1165,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(url), handler);
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
}
@Override
@@ -1202,8 +1234,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withLogging("Page.waitForLoadState",
() -> mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class)));
withWaitLogging("Page.waitForLoadState", () -> {
mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class));
return null;
});
}
@Override
@@ -1270,7 +1304,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(toRequestPredicate(new UrlMatcher(urlGlob)), options, code);
return waitForRequest(toRequestPredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
}
@Override
@@ -1291,12 +1325,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForRequestOptions();
}
List<Waitable<Request>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST,
request -> predicate == null || predicate.test(request)));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return runUntil(code, new WaitableRace<>(waitables));
return waitForEventWithTimeout(EventType.REQUEST, code, predicate, options.timeout);
}
@Override
@@ -1308,18 +1337,12 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForRequestFinishedOptions();
}
List<Waitable<Request>> waitables = new ArrayList<>();
Predicate<Request> predicate = options.predicate;
waitables.add(new WaitableEvent<>(listeners, EventType.REQUESTFINISHED,
request -> predicate == null || predicate.test(request)));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return runUntil(code, new WaitableRace<>(waitables));
return waitForEventWithTimeout(EventType.REQUESTFINISHED, code, options.predicate, options.timeout);
}
@Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(urlGlob)), options, code);
return waitForResponse(toResponsePredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
}
@Override
@@ -1340,12 +1363,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new WaitForResponseOptions();
}
List<Waitable<Response>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE,
response -> predicate == null || predicate.test(response)));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return runUntil(code, new WaitableRace<>(waitables));
return waitForEventWithTimeout(EventType.RESPONSE, code, predicate, options.timeout);
}
@Override
@@ -1361,7 +1379,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
waitForURL(new UrlMatcher(browserContext.baseUrl, url), options);
}
@Override
@@ -23,20 +23,26 @@ import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PlaywrightImpl extends ChannelOwner implements Playwright {
private Process driverProcess;
public static PlaywrightImpl create() {
public static PlaywrightImpl create(CreateOptions options) {
try {
Path driver = Driver.ensureDriverInstalled();
Map<String, String> env = Collections.emptyMap();
if (options != null && options.env != null) {
env = options.env;
}
Path driver = Driver.ensureDriverInstalled(env);
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
// pb.environment().put("DEBUG", "pw:pro*");
pb.environment().putAll(env);
Process p = pb.start();
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
PlaywrightImpl result = connection.initializePlaywright();
result.driverProcess = p;
result.initSharedSelectors(null);
return result;
@@ -0,0 +1,67 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.options.HttpHeader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
class RawHeaders {
private final List<HttpHeader> headersArray;
private final Map<String, List<String>> headersMap = new LinkedHashMap<>();
RawHeaders(List<HttpHeader> headers) {
headersArray = headers;
for (HttpHeader h: headers) {
String name = h.name.toLowerCase();
List<String> values = headersMap.get(name);
if (values == null) {
values = new ArrayList<>();
headersMap.put(name, values);
}
values.add(h.value);
}
}
String get(String name) {
List<String> values = getAll(name);
if (values == null) {
return null;
}
return String.join("set-cookie".equals(name.toLowerCase()) ? "\n" : ", ", values);
}
List<String> getAll(String name) {
return headersMap.get(name.toLowerCase());
}
Map<String, String> headers() {
Map<String, String> result = new LinkedHashMap<>();
for (String name: headersMap.keySet()) {
result.put(name, get(name));
}
return result;
}
List<HttpHeader> headersArray() {
return headersArray;
}
}
@@ -16,25 +16,32 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Response;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.Sizes;
import com.microsoft.playwright.options.Timing;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.toHeadersMap;
import static java.util.Arrays.asList;
public class RequestImpl extends ChannelOwner implements Request {
private final byte[] postData;
private RequestImpl redirectedFrom;
private RequestImpl redirectedTo;
final Map<String, String> headers = new HashMap<>();
private final RawHeaders headers;
private RawHeaders rawHeaders;
String failure;
Timing timing;
boolean didFailOrFinish;
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -43,10 +50,7 @@ public class RequestImpl extends ChannelOwner implements Request {
redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
redirectedFrom.redirectedTo = this;
}
for (JsonElement e : initializer.getAsJsonArray("headers")) {
JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
if (initializer.has("postData")) {
postData = Base64.getDecoder().decode(initializer.get("postData").getAsString());
} else {
@@ -54,19 +58,34 @@ public class RequestImpl extends ChannelOwner implements Request {
}
}
@Override
public Map<String, String> allHeaders() {
return withLogging("Request.allHeaders", () -> getRawHeaders().headers());
}
@Override
public String failure() {
return failure;
}
@Override
public Frame frame() {
public FrameImpl frame() {
return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
}
@Override
public Map<String, String> headers() {
return headers;
return headers.headers();
}
@Override
public List<HttpHeader> headersArray() {
return withLogging("Request.headersArray", () -> getRawHeaders().headersArray());
}
@Override
public String headerValue(String name) {
return withLogging("Request.headerValue", () -> getRawHeaders().get(name));
}
@Override
@@ -108,7 +127,7 @@ public class RequestImpl extends ChannelOwner implements Request {
}
@Override
public Response response() {
public ResponseImpl response() {
return withLogging("Request.response", () -> {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
@@ -118,6 +137,18 @@ public class RequestImpl extends ChannelOwner implements Request {
});
}
@Override
public Sizes sizes() {
return withLogging("Request.sizes", () -> {
ResponseImpl response = response();
if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request");
}
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
});
}
@Override
public Timing timing() {
return timing;
@@ -132,4 +163,17 @@ public class RequestImpl extends ChannelOwner implements Request {
return redirectedTo != null ? redirectedTo.finalRequest() : this;
}
private RawHeaders getRawHeaders() {
if (rawHeaders != null) {
return rawHeaders;
}
JsonArray rawHeadersJson = withLogging("Request.allHeaders", () -> {
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
return result.getAsJsonArray("headers");
});
// The field may have been initialized in a nested call but it is ok.
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
return rawHeaders;
}
}
@@ -16,37 +16,38 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Response;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.SecurityDetails;
import com.microsoft.playwright.options.ServerAddr;
import com.microsoft.playwright.options.Timing;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
import static java.util.Arrays.asList;
public class ResponseImpl extends ChannelOwner implements Response {
private final Map<String, String> headers = new HashMap<>();
private final RawHeaders headers;
private RawHeaders rawHeaders;
private final RequestImpl request;
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
for (JsonElement e : initializer.getAsJsonArray("headers")) {
JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
request.headers.clear();
for (JsonElement e : initializer.getAsJsonArray("requestHeaders")) {
JsonObject item = e.getAsJsonObject();
request.headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
request.timing = Serialization.gson().fromJson(initializer.get("timing"), Timing.class);
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
}
@Override
public Map<String, String> allHeaders() {
return withLogging("Response.allHeaders", () -> getRawHeaders().headers());
}
@Override
@@ -59,13 +60,22 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public String finished() {
return withLogging("Response.finished", () -> {
JsonObject json = sendMessage("finished").getAsJsonObject();
if (json.has("error")) {
return json.get("error").getAsString();
List<Waitable<String>> waitables = new ArrayList<>();
waitables.add(new WaitableNever<String>() {
@Override
public boolean isDone() {
return request.didFailOrFinish;
}
@Override
public String get() {
return request.failure();
}
return null;
});
PageImpl page = request.frame().page;
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(null));
runUntil(() -> {}, new WaitableRace<>(waitables));
return request.failure();
}
@Override
@@ -75,7 +85,22 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public Map<String, String> headers() {
return headers;
return headers.headers();
}
@Override
public List<HttpHeader> headersArray() {
return withLogging("Response.headersArray", () -> getRawHeaders().headersArray());
}
@Override
public String headerValue(String name) {
return getRawHeaders().get(name);
}
@Override
public List<String> headerValues(String name) {
return getRawHeaders().getAll(name);
}
@Override
@@ -88,6 +113,28 @@ public class ResponseImpl extends ChannelOwner implements Response {
return request;
}
@Override
public SecurityDetails securityDetails() {
return withLogging("Response.securityDetails", () -> {
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class);
}
return null;
});
}
@Override
public ServerAddr serverAddr() {
return withLogging("Response.serverAddr", () -> {
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class);
}
return null;
});
}
@Override
public int status() {
return initializer.get("status").getAsInt();
@@ -107,4 +154,12 @@ public class ResponseImpl extends ChannelOwner implements Response {
public String url() {
return initializer.get("url").getAsString();
}
private RawHeaders getRawHeaders() {
if (rawHeaders == null) {
JsonObject json = sendMessage("rawResponseHeaders").getAsJsonObject();
rawHeaders = new RawHeaders(asList(gson().fromJson(json.getAsJsonArray("headers"), HttpHeader[].class)));
}
return rawHeaders;
}
}
@@ -29,15 +29,31 @@ class Router {
private static class RouteInfo {
final UrlMatcher matcher;
final Consumer<Route> handler;
Integer times;
RouteInfo(UrlMatcher matcher, Consumer<Route> handler) {
RouteInfo(UrlMatcher matcher, Consumer<Route> handler, Integer times) {
this.matcher = matcher;
this.handler = handler;
this.times = times;
}
boolean handle(Route route) {
if (times != null && times <= 0) {
return false;
}
if (!matcher.test(route.request().url())) {
return false;
}
if (times != null) {
--times;
}
handler.accept(route);
return true;
}
}
void add(UrlMatcher matcher, Consumer<Route> handler) {
routes.add(new RouteInfo(matcher, handler));
void add(UrlMatcher matcher, Consumer<Route> handler, Integer times) {
routes.add(0, new RouteInfo(matcher, handler, times));
}
void remove(UrlMatcher matcher, Consumer<Route> handler) {
@@ -52,8 +68,7 @@ class Router {
boolean handle(Route route) {
for (RouteInfo info : routes) {
if (info.matcher.test(route.request().url())) {
info.handler.accept(route);
if (info.handle(route)) {
return true;
}
}
@@ -39,9 +39,11 @@ class Serialization {
if (gson == null) {
gson = new GsonBuilder()
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new BrowserChannelSerializer())
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
.registerTypeAdapter(Media.class, new MediaSerializer())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
@@ -255,6 +257,8 @@ class Serialization {
private static boolean isSupported(Type type) {
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
}
@@ -303,11 +307,10 @@ class Serialization {
return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-'));
}
}
private static class MediaSerializer implements JsonSerializer<Media> {
private static class ToLowerCaseAndDashSerializer<E extends Enum<E>> implements JsonSerializer<E> {
@Override
public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString().toLowerCase());
public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-'));
}
}
@@ -351,37 +354,5 @@ class Serialization {
return SameSiteAttribute.valueOf(value.toUpperCase());
}
}
private static class ColorSchemeAdapter extends TypeAdapter<ColorScheme> {
@Override
public void write(JsonWriter out, ColorScheme value) throws IOException {
String stringValue;
switch (value) {
case DARK:
stringValue = "dark";
break;
case LIGHT:
stringValue = "light";
break;
case NO_PREFERENCE:
stringValue = "no-preference";
break;
default:
throw new PlaywrightException("Unexpected value: " + value);
}
out.value(stringValue);
}
@Override
public ColorScheme read(JsonReader in) throws IOException {
String value = in.nextString();
switch (value) {
case "dark": return ColorScheme.DARK;
case "light": return ColorScheme.LIGHT;
case "no-preference": return ColorScheme.NO_PREFERENCE;
default: throw new PlaywrightException("Unexpected value: " + value);
}
}
}
}
@@ -45,6 +45,9 @@ public class Stream extends ChannelOwner {
@Override
public int read(byte[] b, int off, int len) {
if (len == 0) {
return 0;
}
JsonObject params = new JsonObject();
params.addProperty("size", len);
JsonObject json = sendMessage("read", params).getAsJsonObject();
@@ -31,10 +31,17 @@ class TracingImpl implements Tracing {
this.context = context;
}
private void export(Path path) {
JsonObject json = context.sendMessage("tracingExport").getAsJsonObject();
private void stopChunkImpl(Path path) {
JsonObject params = new JsonObject();
params.addProperty("save", path != null);
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
if (!json.has("artifact")) {
return;
}
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
if (context.browser().isRemote) {
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (context.browser() != null && context.browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(path);
@@ -46,21 +53,34 @@ class TracingImpl implements Tracing {
context.withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk() {
context.withLogging("Tracing.startChunk", () -> {
context.sendMessage("tracingStartChunk");
});
}
private void startImpl(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
context.sendMessage("tracingStart", params);
context.sendMessage("tracingStartChunk");
}
@Override
public void stop(StopOptions options) {
context.withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
context.sendMessage("tracingStop");
if (options != null && options.path != null) {
export(options.path);
}
});
}
@Override
public void stopChunk(StopChunkOptions options) {
context.withLogging("Tracing.stopChunk", () -> {
stopChunkImpl(options == null ? null : options.path);
});
}
}
@@ -18,6 +18,8 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -33,15 +35,15 @@ class UrlMatcher {
}
static UrlMatcher any() {
return new UrlMatcher(null, null);
return new UrlMatcher((Object) null, null);
}
static UrlMatcher forOneOf(Object object) {
static UrlMatcher forOneOf(URL baseUrl, Object object) {
if (object == null) {
return UrlMatcher.any();
}
if (object instanceof String) {
return new UrlMatcher((String) object);
return new UrlMatcher(baseUrl, (String) object);
}
if (object instanceof Pattern) {
return new UrlMatcher((Pattern) object);
@@ -52,8 +54,19 @@ class UrlMatcher {
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
}
UrlMatcher(String url) {
this(url, toPredicate(Pattern.compile(globToRegex(url))).or(s -> url == null || url.equals(s)));
private static String resolveUrl(URL baseUrl, String spec) {
if (baseUrl == null) {
return spec;
}
try {
return new URL(baseUrl, spec).toString();
} catch (MalformedURLException e) {
return spec;
}
}
UrlMatcher(URL base, String url) {
this(url, toPredicate(Pattern.compile(globToRegex(resolveUrl(base, url)))).or(s -> url == null || url.equals(s)));
}
UrlMatcher(Pattern pattern) {
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.*;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -187,4 +188,12 @@ class Utils {
}
return result.toString();
}
static Map<String, String> toHeadersMap(List<HttpHeader> headers) {
Map<String, String> map = new LinkedHashMap<>();
for (HttpHeader header: headers) {
map.put(header.name.toLowerCase(), header.value);
}
return map;
}
}
@@ -24,7 +24,7 @@ import java.nio.file.Paths;
import static java.util.Arrays.asList;
class VideoImpl extends LoggingSupport implements Video {
class VideoImpl implements Video {
private final PageImpl page;
private final WaitableResult<ArtifactImpl> waitableArtifact = new WaitableResult<>();
private final boolean isRemote;
@@ -47,7 +47,7 @@ class VideoImpl extends LoggingSupport implements Video {
@Override
public void delete() {
withLogging("Video.delete", () -> {
page.withLogging("Video.delete", () -> {
try {
waitForArtifact().delete();
} catch (PlaywrightException e) {
@@ -57,7 +57,7 @@ class VideoImpl extends LoggingSupport implements Video {
@Override
public Path path() {
return withLogging("Video.path", () -> {
return page.withLogging("Video.path", () -> {
if (isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
}
@@ -71,7 +71,7 @@ class VideoImpl extends LoggingSupport implements Video {
@Override
public void saveAs(Path path) {
withLogging("Video.saveAs", () -> {
page.withLogging("Video.saveAs", () -> {
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
@@ -53,10 +53,10 @@ public class WaitForEventLogger<T> implements Supplier<T> {
}
private void sendWaitForEventInfo(JsonObject info) {
info.addProperty("apiName", apiName);
info.addProperty("event", "");
info.addProperty("waitId", waitId);
JsonObject params = new JsonObject();
params.add("info", info);
channel.sendMessageAsync("waitForEventInfo", params);
channel.withLogging(apiName, () -> channel.sendMessageAsync("waitForEventInfo", params));
}
}
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
class WebSocketImpl extends ChannelOwner implements WebSocket {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
@@ -93,7 +94,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
if (options == null) {
options = new WaitForFrameReceivedOptions();
}
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.timeout);
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.predicate, options.timeout);
}
@Override
@@ -105,7 +106,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
if (options == null) {
options = new WaitForFrameSentOptions();
}
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.timeout);
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.predicate, options.timeout);
}
@Override
@@ -140,9 +141,9 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
private WebSocketFrame waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<WebSocketFrame> predicate, Double timeout) {
List<Waitable<WebSocketFrame>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
waitables.add(new WaitableWebSocketClose<>());
waitables.add(new WaitableWebSocketError<>());
waitables.add(page.createWaitForCloseHelper());
@@ -176,14 +177,22 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
void handleEvent(String event, JsonObject parameters) {
switch (event) {
case "frameSent": {
int opCode = parameters.get("opcode").getAsInt();
if (opCode != 1 && opCode != 2) {
break;
}
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
parameters.get("data").getAsString(), opCode == 2);
listeners.notify(EventType.FRAMESENT, WebSocketFrame);
break;
}
case "frameReceived": {
int opCode = parameters.get("opcode").getAsInt();
if (opCode != 1 && opCode != 2) {
break;
}
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
parameters.get("data").getAsString(), opCode == 2);
listeners.notify(EventType.FRAMERECEIVED, WebSocketFrame);
break;
}
@@ -52,30 +52,51 @@ public class Cookie {
this.name = name;
this.value = value;
}
/**
* either url or domain / path are required. Optional.
*/
public Cookie setUrl(String url) {
this.url = url;
return this;
}
/**
* either url or domain / path are required Optional.
*/
public Cookie setDomain(String domain) {
this.domain = domain;
return this;
}
/**
* either url or domain / path are required Optional.
*/
public Cookie setPath(String path) {
this.path = path;
return this;
}
/**
* Unix time in seconds. Optional.
*/
public Cookie setExpires(double expires) {
this.expires = expires;
return this;
}
/**
* Optional.
*/
public Cookie setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
/**
* Optional.
*/
public Cookie setSecure(boolean secure) {
this.secure = secure;
return this;
}
/**
* Optional.
*/
public Cookie setSameSite(SameSiteAttribute sameSite) {
this.sameSite = sameSite;
return this;
@@ -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 ForcedColors {
ACTIVE,
NONE
}
@@ -34,6 +34,9 @@ public class Geolocation {
this.latitude = latitude;
this.longitude = longitude;
}
/**
* Non-negative accuracy value. Defaults to {@code 0}.
*/
public Geolocation setAccuracy(double accuracy) {
this.accuracy = accuracy;
return this;
@@ -0,0 +1,29 @@
/*
* 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 class HttpHeader {
/**
* Name of the header.
*/
public String name;
/**
* Value of the header.
*/
public String value;
}
@@ -34,18 +34,30 @@ public class Margin {
*/
public String left;
/**
* Top margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public Margin setTop(String top) {
this.top = top;
return this;
}
/**
* Right margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public Margin setRight(String right) {
this.right = right;
return this;
}
/**
* Bottom margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public Margin setBottom(String bottom) {
this.bottom = bottom;
return this;
}
/**
* Left margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public Margin setLeft(String left) {
this.left = left;
return this;
@@ -38,14 +38,23 @@ public class Proxy {
public Proxy(String server) {
this.server = server;
}
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public Proxy setBypass(String bypass) {
this.bypass = bypass;
return this;
}
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public Proxy setUsername(String username) {
this.username = username;
return this;
}
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public Proxy setPassword(String password) {
this.password = password;
return this;
@@ -0,0 +1,43 @@
/*
* 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 class SecurityDetails {
/**
* Common Name component of the Issuer field. from the certificate. This should only be used for informational purposes.
* Optional.
*/
public String issuer;
/**
* The specific TLS protocol used. (e.g. {@code TLS 1.3}). Optional.
*/
public String protocol;
/**
* Common Name component of the Subject field from the certificate. This should only be used for informational purposes.
* Optional.
*/
public String subjectName;
/**
* Unix timestamp (in seconds) specifying when this cert becomes valid. Optional.
*/
public Double validFrom;
/**
* Unix timestamp (in seconds) specifying when this cert becomes invalid. Optional.
*/
public Double validTo;
}
@@ -30,14 +30,23 @@ public class SelectOption {
*/
public Integer index;
/**
* Matches by {@code option.value}. Optional.
*/
public SelectOption setValue(String value) {
this.value = value;
return this;
}
/**
* Matches by {@code option.label}. Optional.
*/
public SelectOption setLabel(String label) {
this.label = label;
return this;
}
/**
* Matches by the index. Optional.
*/
public SelectOption setIndex(int index) {
this.index = index;
return this;
@@ -0,0 +1,26 @@
/*
* 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 class ServerAddr {
/**
* IPv4 or IPV6 address of the server.
*/
public String ipAddress;
public int port;
}
@@ -0,0 +1,37 @@
/*
* 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 class Sizes {
/**
* Size of the request body (POST data payload) in bytes. Set to 0 if there was no body.
*/
public int requestBodySize;
/**
* Total number of bytes from the start of the HTTP request message until (and including) the double CRLF before the body.
*/
public int requestHeadersSize;
/**
* Size of the received response body (encoded) in bytes.
*/
public int responseBodySize;
/**
* Total number of bytes from the start of the HTTP response message until (and including) the double CRLF before the body.
*/
public int responseHeadersSize;
}
@@ -189,6 +189,9 @@ public class Server implements HttpHandler {
if (csp.containsKey(path)) {
exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path));
}
if ("/".equals(path)) {
path = "/index.html";
}
File file = new File(resourcesDir, path.substring(1));
if (!file.exists()) {
exchange.sendResponseHeaders(404, 0);
@@ -16,7 +16,6 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.BrowserChannel;
import org.junit.jupiter.api.*;
import java.io.IOException;
@@ -0,0 +1,30 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
public class TestBeforeunload extends TestBase {
@Test
void shouldBeAbleToNavigateAwayFromPageWithBeforeunload() {
page.navigate(server.PREFIX + "/beforeunload.html");
// We have to interact with a page so that "beforeunload" handlers
// fire.
page.click("body");
page.navigate(server.EMPTY_PAGE);
}
}
@@ -26,6 +26,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static com.microsoft.playwright.Utils.assertJsonEquals;
import static com.microsoft.playwright.Utils.getOS;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -217,7 +218,7 @@ public class TestBrowserContextAddCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
"}]", cookies);
}
@@ -236,7 +237,7 @@ public class TestBrowserContextAddCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
"}]", cookies);
assertEquals("gridcookie=GRID", page.evaluate("document.cookie"));
page.navigate(server.EMPTY_PAGE);
@@ -309,7 +310,7 @@ public class TestBrowserContextAddCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: true,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
"}]", context.cookies("https://www.example.com"));
}
@@ -345,7 +346,7 @@ public class TestBrowserContextAddCookies extends TestBase {
"}", server.CROSS_PROCESS_PREFIX + "/grid.html");
page.frames().get(1).evaluate("document.cookie = 'username=John Doe'");
page.waitForTimeout(2000);
boolean allowsThirdParty = isChromium() || isFirefox();
boolean allowsThirdParty = isFirefox();
List<Cookie> cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html");
if (allowsThirdParty) {
assertJsonEquals("[{\n" +
@@ -0,0 +1,79 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.net.MalformedURLException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class TestBrowserContextBaseUrl extends TestBase {
@Test
void shouldConstructANewURLWhenABaseURLInBrowserNewContextIsPassedToPageGoto() throws MalformedURLException {
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) {
Page page = context.newPage();
assertEquals(server.EMPTY_PAGE, page.navigate("/empty.html").url());
}
}
@Test
void shouldConstructANewURLWhenABaseURLInBrowserNewPageIsPassedToPageGoto() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX))) {
assertEquals(server.EMPTY_PAGE, page.navigate("/empty.html").url());
}
}
@Test
void shouldConstructTheURLsCorrectlyWhenABaseURLWithoutATrailingSlashInBrowserNewPageIsPassedToPageGoto() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction"))) {
assertEquals(server.PREFIX + "/mypage.html", page.navigate("mypage.html").url());
assertEquals(server.PREFIX + "/mypage.html", page.navigate("./mypage.html").url());
assertEquals(server.PREFIX + "/mypage.html", page.navigate("/mypage.html").url());
}
}
@Test
void shouldConstructTheURLsCorrectlyWhenABaseURLWithATrailingSlashInBrowserNewPageIsPassedToPageGoto() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction/"))) {
assertEquals(server.PREFIX + "/url-construction/mypage.html", page.navigate("mypage.html").url());
assertEquals(server.PREFIX + "/url-construction/mypage.html", page.navigate("./mypage.html").url());
assertEquals(server.PREFIX + "/mypage.html", page.navigate("/mypage.html").url());
assertEquals(server.PREFIX + "/url-construction/", page.navigate(".").url());
assertEquals(server.PREFIX + "/", page.navigate("/").url());
}
}
@Test
void shouldNotConstructANewURLWhenValidURLsArePassed() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL("http://microsoft.com"))) {
assertEquals(server.EMPTY_PAGE, page.navigate(server.EMPTY_PAGE).url());
page.navigate("data:text/html,Hello world");
assertEquals("data:text/html,Hello world", page.evaluate("window.location.href"));
page.navigate("about:blank");
assertEquals("about:blank", page.evaluate("window.location.href"));
}
}
@Test
void shouldBeAbleToMatchAURLRelativeToItsGivenURLWithUrlMatcher() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/foobar/"))) {
page.navigate("/kek/index.html");
page.waitForURL("/kek/index.html");
assertEquals(server.PREFIX + "/kek/index.html", page.url());
page.route("./kek/index.html", route -> route.fulfill(new Route.FulfillOptions().setBody("base-url-matched-route")));
Request[] request = {null};
Response response = page.waitForResponse("./kek/index.html", () -> {
request[0] = page.waitForRequest("./kek/index.html", () -> {
page.navigate("./kek/index.html");
});
});
assertNotNull(request[0]);
assertNotNull(response);
assertEquals(server.PREFIX + "/foobar/kek/index.html", request[0].url());
assertEquals(server.PREFIX + "/foobar/kek/index.html", response.url());
assertEquals("base-url-matched-route", response.text());
}
}
}
@@ -115,10 +115,9 @@ public class TestBrowserContextBasic extends TestBase {
@Test
@Disabled("TODO: supported null viewport option")
void shouldNotAllowDeviceScaleFactorWithNullViewport() {
try {
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0));
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0).setViewportSize(null));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
@@ -126,10 +125,9 @@ public class TestBrowserContextBasic extends TestBase {
}
@Test
@Disabled("TODO: supported null viewport option")
void shouldNotAllowIsMobileWithNullViewport() {
try {
browser.newContext(new Browser.NewContextOptions().setIsMobile(true));
browser.newContext(new Browser.NewContextOptions().setIsMobile(true).setViewportSize(null));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
@@ -50,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" }]", cookies);
}
@@ -74,7 +74,11 @@ public class TestBrowserContextCookies extends TestBase {
assertEquals(timestamp, cookie.expires);
assertEquals(false, cookie.httpOnly);
assertEquals(false, cookie.secure);
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
if (isChromium()) {
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
} else {
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
}
}
@Test
@@ -142,7 +146,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" },\n" +
" {\n" +
" name: 'username',\n" +
@@ -152,7 +156,7 @@ public class TestBrowserContextCookies extends TestBase {
" expires: -1,\n" +
" httpOnly: false,\n" +
" secure: false,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
" }\n" +
"]", cookies);
}
@@ -170,19 +174,19 @@ public class TestBrowserContextCookies extends TestBase {
" value: 'tweets',\n" +
" domain: 'baz.com',\n" +
" path: '/',\n" +
" expires: -1,\n" +
" expires: -1.0,\n" +
" httpOnly: false,\n" +
" secure: true,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
"}, {\n" +
" name: 'doggo',\n" +
" value: 'woofs',\n" +
" domain: 'foo.com',\n" +
" path: '/',\n" +
" expires: -1,\n" +
" expires: -1.0,\n" +
" httpOnly: false,\n" +
" secure: true,\n" +
" sameSite: 'NONE'\n" +
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
"}]", cookies);
}
@@ -200,9 +204,17 @@ public class TestBrowserContextCookies extends TestBase {
page.navigate(server.EMPTY_PAGE);
Object documentCookie = page.evaluate("document.cookie.split('; ').sort().join('; ')");
assertEquals("one=uno; three=tres; two=dos", documentCookie);
if (isChromium()) {
assertEquals("one=uno; two=dos", documentCookie);
} else {
assertEquals("one=uno; three=tres; two=dos", documentCookie);
}
List<SameSiteAttribute> list = context.cookies().stream().map(c -> c.sameSite).sorted().collect(Collectors.toList());
assertEquals(asList( SameSiteAttribute.STRICT, SameSiteAttribute.LAX, SameSiteAttribute.NONE), list);
if (isChromium()) {
assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX), list);
} else {
assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX, SameSiteAttribute.NONE), list);
}
}
}
@@ -57,11 +57,10 @@ public class TestBrowserContextRoute extends TestBase {
Page page = context.newPage();
List<Integer> intercepted = new ArrayList<>();
Consumer<Route> handler1 = route -> {
context.route("**/*", route -> {
intercepted.add(1);
route.resume();
};
context.route("**/empty.html", handler1);
});
context.route("**/empty.html", route -> {
intercepted.add(2);
route.resume();
@@ -70,22 +69,23 @@ public class TestBrowserContextRoute extends TestBase {
intercepted.add(3);
route.resume();
});
context.route("**/*", route -> {
Consumer<Route> handler4 = route -> {
intercepted.add(4);
route.resume();
});
};
context.route("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(1), intercepted);
assertEquals(asList(4), intercepted);
intercepted.clear();
context.unroute("**/empty.html", handler1);
context.unroute("**/empty.html", handler4);
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(2), intercepted);
assertEquals(asList(3), intercepted);
intercepted.clear();
context.unroute("**/empty.html");
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(4), intercepted);
assertEquals(asList(1), intercepted);
context.close();
}
@@ -121,4 +121,17 @@ public class TestBrowserContextRoute extends TestBase {
assertEquals("context", response.text());
context.close();
}
@Test
void shouldSupportTheTimesParameterWithRouteMatching() {
int[] intercepted = {0};
context.route("**/empty.html", route -> {
++intercepted[0];
route.resume();
}, new BrowserContext.RouteOptions().setTimes(2));
page.navigate(server.EMPTY_PAGE);
page.navigate(server.EMPTY_PAGE);
page.navigate(server.EMPTY_PAGE);
assertEquals(2, intercepted[0]);
}
}
@@ -100,24 +100,24 @@ public class TestBrowserContextStorageState extends TestBase {
context.storageState(new BrowserContext.StorageStateOptions().setPath(path));
JsonObject expected = new Gson().fromJson(
"{\n" +
" \"cookies\":[\n" +
" 'cookies':[\n" +
" { \n" +
" \"name\":\"username\",\n" +
" \"value\":\"John Doe\",\n" +
" \"domain\":\"www.example.com\",\n" +
" \"path\":\"/\",\n" +
" \"expires\":-1,\n" +
" \"httpOnly\":false,\n" +
" \"secure\":false,\n" +
" \"sameSite\":\"None\"\n" +
" 'name':'username',\n" +
" 'value':'John Doe',\n" +
" 'domain':'www.example.com',\n" +
" 'path':'/',\n" +
" 'expires':-1,\n" +
" 'httpOnly':false,\n" +
" 'secure':false,\n" +
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
" }],\n" +
" \"origins\":[\n" +
" 'origins':[\n" +
" {\n" +
" \"origin\":\"https://www.example.com\",\n" +
" \"localStorage\":[\n" +
" 'origin':'https://www.example.com',\n" +
" 'localStorage':[\n" +
" {\n" +
" \"name\":\"name1\",\n" +
" \"value\":\"value1\"\n" +
" 'name':'name1',\n" +
" 'value':'value1'\n" +
" }]\n" +
" }]\n" +
"}\n", JsonObject.class);
@@ -0,0 +1,54 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextStrict extends TestBase {
@Override
BrowserContext createContext() {
return browser.newContext(new Browser.NewContextOptions().setStrictSelectors(true));
}
@Test
void shouldNotFailPageTextContentInNonStrictMode() {
try (BrowserContext context = browser.newContext()) {
Page page = context.newPage();
page.setContent("<span>span1</span><div><span>target</span></div>");
assertEquals("span1", page.textContent("span"));
}
}
@Test
void shouldFailPageTextContentInStrictMode() {
page.setContent("<span>span1</span><div><span>target</span></div>");
try {
page.textContent("span");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("strict mode violation"));
}
}
@Test
void shouldOptOutOfStrictMode() {
page.setContent("<span>span1</span><div><span>target</span></div>");
assertEquals("span1", page.textContent("span", new Page.TextContentOptions().setStrict(false)));
}
}
@@ -31,11 +31,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeConnect extends TestBase {
@@ -60,7 +59,7 @@ public class TestBrowserTypeConnect extends TestBase {
private static BrowserServer launchBrowserServer(BrowserType browserType) {
try {
Path driver = Driver.ensureDriverInstalled();
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
Path dir = driver.getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
@@ -458,4 +457,24 @@ public class TestBrowserTypeConnect extends TestBase {
}
page.close();
}
@Test
void shouldSupportTracingOverWebSocket(@TempDir Path tempDir) throws IOException {
List<BrowserContext> contexts = browser.contexts();
assertEquals(1, contexts.size());
BrowserContext context = contexts.get(0);
Page page = context.newPage();
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
page.close();
Path traceFile = tempDir.resolve("trace.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
assertTrue(Files.exists(traceFile));
assertTrue(Files.size(traceFile) > 0);
}
}
@@ -20,6 +20,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.io.InputStream;
@@ -27,12 +28,13 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.*;
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-specific API")
public class TestChromium extends TestBase {
@@ -41,7 +43,7 @@ public class TestChromium extends TestBase {
// Do not create anything.
}
private static int nextPort = 9339;
private int nextPort = 9339;
private static String wsEndpointFromUrl(String urlString) throws IOException {
URL url = new URL(urlString);
@@ -122,4 +124,30 @@ public class TestChromium extends TestBase {
assertEquals("bar", webSocketServer.lastClientHandshake.getFieldValue("foo"));
}
}
@Test
void shouldSupportTracingOverCDP(@TempDir Path tempDir) throws IOException {
int port = nextPort++;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
try (Browser cdpBrowser = browserType.connectOverCDP("http://localhost:" + port)) {
List<BrowserContext> contexts = cdpBrowser.contexts();
assertEquals(1, contexts.size());
BrowserContext context = contexts.get(0);
Page page = context.newPage();
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
page.close();
Path traceFile = tempDir.resolve("trace.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
assertTrue(Files.exists(traceFile));
assertTrue(Files.size(traceFile) > 0);
}
}
}
}
@@ -30,6 +30,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import static com.microsoft.playwright.options.KeyboardModifier.ALT;
import static com.microsoft.playwright.Utils.copy;
@@ -58,6 +59,54 @@ public class TestDownload extends TestBase {
writer.write("Hello world");
}
});
server.setRoute("/downloadWithDelay", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=file.txt");
exchange.sendResponseHeaders(200, 0);
// Chromium requires a large enough payload to trigger the download event soon enough
OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody());
writer.write(String.join("", Collections.nCopies(4096, "a")));
writer.write("foo");
writer.flush();
});
}
@Test
void shouldReportDownloadWhenNavigationTurnsIntoDownload() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
Response[] response = new Response[]{null};
PlaywrightException[] error = new PlaywrightException[]{null};
Download download = page.waitForDownload(() -> {
try {
response[0] = page.navigate(server.PREFIX + "/download");
} catch (PlaywrightException e) {
error[0] = e;
}
});
assertEquals(page, download.page());
assertEquals(server.PREFIX + "/download", download.url());
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
assertEquals("Hello world", new String(bytes, UTF_8));
if (isChromium()) {
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("net::ERR_ABORTED"));
assertEquals("about:blank", page.url());
} else if (isWebKit()) {
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("Download is starting"));
assertEquals("about:blank", page.url());
} else {
assertNotNull(response[0]);
assertEquals(200, response[0].status());
assertEquals(server.PREFIX + "/download", page.url());
}
page.close();
}
@Test
@@ -354,6 +403,19 @@ public class TestDownload extends TestBase {
page.close();
}
@Test
void streamShouldSupportZeroSizeRead() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
InputStream stream = download.createReadStream();
byte[] b = new byte[1];
int read = stream.read(b, 0, 0);
assertEquals(0, read);
page.close();
}
@Test
void shouldDeleteDownloadsOnContextDestruction() {
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
@@ -385,4 +447,30 @@ public class TestDownload extends TestBase {
assertFalse(Files.exists(path2));
assertFalse(Files.exists(path1.resolve("..")));
}
@Test
void shouldBeAbleToCancelPendingDownloads() {
try (Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true))) {
page.setContent("<a href='" + server.PREFIX + "/downloadWithDelay'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
download.cancel();
String failure = download.failure();
assertEquals("canceled", failure);
}
}
@Test
void shouldNotFailExplicitlyToCancelADownloadEvenIfThatIsAlreadyFinished() throws IOException {
try (Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true))) {
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
assertEquals("Hello world", new String(bytes, UTF_8));
download.cancel();
assertNull(download.failure());
}
}
}
@@ -45,6 +45,33 @@ public class TestElementHandleConvenience extends TestBase {
assertNull(page.getAttribute("#outer", "foo"));
}
@Test
void inputValueShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
page.fill("#textarea", "text value");
assertEquals("text value", page.inputValue("#textarea"));
page.fill("#input", "input value");
assertEquals("input value", page.inputValue("#input"));
ElementHandle handle = page.querySelector("#input");
assertEquals("input value", handle.inputValue());
try {
page.inputValue("#inner");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
}
ElementHandle handle2 = page.querySelector("#inner");
try {
handle2.inputValue();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
}
}
@Test
void innerHTMLShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
@@ -0,0 +1,86 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestElementHandleMisc extends TestBase {
@Test
void shouldHover() {
page.navigate(server.PREFIX + "/input/scrollable.html");
ElementHandle button = page.querySelector("#button-6");
button.hover();
assertEquals("button-6", page.evaluate("document.querySelector('button:hover').id"));
}
@Test
void shouldHoverWhenNodeIsRemoved() {
page.navigate(server.PREFIX + "/input/scrollable.html");
page.evaluate("() => delete window['Node']");
ElementHandle button = page.querySelector("#button-6");
button.hover();
assertEquals("button-6", page.evaluate("document.querySelector('button:hover').id"));
}
@Test
void shouldFillInput() {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle handle = page.querySelector("input");
handle.fill("some value");
assertEquals("some value", page.evaluate("window['result']"));
}
@Test
void shouldFillInputWhenNodeIsRemoved() {
page.navigate(server.PREFIX + "/input/textarea.html");
page.evaluate("() => delete window['Node']");
ElementHandle handle = page.querySelector("input");
handle.fill("some value");
assertEquals("some value", page.evaluate("window['result']"));
}
@Test
void shouldCheckTheBox() {
page.setContent("<input id='checkbox' type='checkbox'></input>");
ElementHandle input = page.querySelector("input");
input.check();
assertEquals(true, page.evaluate("checkbox.checked"));
}
@Test
void shouldUncheckTheBox() {
page.setContent("<input id='checkbox' type='checkbox' checked></input>");
ElementHandle input = page.querySelector("input");
input.uncheck();
assertEquals(false, page.evaluate("checkbox.checked"));
}
@Test
void shouldSelectSingleOption() {
page.navigate(server.PREFIX + "/input/select.html");
ElementHandle select = page.querySelector("select");
select.selectOption("blue");
assertEquals(asList("blue"), page.evaluate("window['result'].onInput"));
assertEquals(asList("blue"), page.evaluate("window['result'].onChange"));
}
@Test
void shouldFocusAButton() {
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
assertEquals(false, button.evaluate("button => document.activeElement === button"));
button.focus();
assertEquals(true, button.evaluate("button => document.activeElement === button"));
}
@Test
void shouldCheckTheBoxUsingSetChecked() {
page.setContent("<input id='checkbox' type='checkbox'></input>");
ElementHandle input = page.querySelector("input");
input.setChecked(true);
assertEquals(true, page.evaluate("checkbox.checked"));
input.setChecked(false);
assertEquals(false, page.evaluate("checkbox.checked"));
}
}
@@ -20,15 +20,20 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import static com.microsoft.playwright.Utils.getOS;
import static com.microsoft.playwright.options.LoadState.DOMCONTENTLOADED;
import static org.junit.jupiter.api.Assertions.*;
@@ -99,7 +104,7 @@ public class TestHar extends TestBase {
assertEquals(1, log.getAsJsonArray("pages").size());
JsonObject pageEntry = log.getAsJsonArray("pages").get(0).getAsJsonObject();
assertEquals("page_0", pageEntry.get("id").getAsString());
assertNotNull(pageEntry.get("id").getAsString());
assertEquals("Hello", pageEntry.get("title").getAsString());
// expect(new Date(pageEntry.startedDateTime).valueOf()).toBeGreaterThan(Date.now() - 3600 * 1000);
assertTrue(pageEntry.getAsJsonObject("pageTimings").get("onContentLoad").getAsDouble() > 0);
@@ -125,7 +130,7 @@ public class TestHar extends TestBase {
}
assertEquals(1, log.getAsJsonArray("pages").size());
JsonObject pageEntry = log.getAsJsonArray("pages").get(0).getAsJsonObject();
assertEquals("page_0", pageEntry.get("id").getAsString());
assertNotNull(pageEntry.get("id").getAsString());
assertEquals("Hello", pageEntry.get("title").getAsString());
}
@@ -135,7 +140,8 @@ public class TestHar extends TestBase {
JsonObject log = pageWithHar.log();
assertEquals(1, log.getAsJsonArray("entries").size());
JsonObject entry = log.getAsJsonArray("entries").get(0).getAsJsonObject();
assertEquals("page_0", entry.get("pageref").getAsString());
String id = log.getAsJsonArray("pages").get(0).getAsJsonObject().get("id").getAsString();
assertEquals(id, entry.get("pageref").getAsString());
assertEquals(server.EMPTY_PAGE, entry.getAsJsonObject("request").get("url").getAsString());
assertEquals("GET", entry.getAsJsonObject("request").get("method").getAsString());
assertEquals("HTTP/1.1", entry.getAsJsonObject("request").get("httpVersion").getAsString());
@@ -0,0 +1,57 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestLocatorClick extends TestBase {
@Test
void shouldWork() {
page.navigate(server.PREFIX + "/input/button.html");
Locator button = page.locator("button");
button.click();
assertEquals("Clicked", page.evaluate("() => window['result']"));
}
@Test
void shouldWorkWithNodeRemoved() {
page.navigate(server.PREFIX + "/input/button.html");
page.evaluate("() => delete window['Node']");
Locator button = page.locator("button");
button.click();
assertEquals("Clicked", page.evaluate("() => window['result']"));
}
@Test
void shouldDoubleClickTheButton() {
page.navigate(server.PREFIX + "/input/button.html");
page.evaluate("() => {\n" +
" window['double'] = false;\n" +
" const button = document.querySelector('button');\n" +
" button.addEventListener('dblclick', event => {\n" +
" window['double'] = true;\n" +
" });\n" +
"}");
Locator button = page.locator("button");
button.dblclick();
assertEquals(true, page.evaluate("double"));
assertEquals("Clicked", page.evaluate("result"));
}
}
@@ -0,0 +1,206 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestLocatorConvenience extends TestBase {
@Test
void shouldHaveANicePreview() {
page.navigate(server.PREFIX + "/dom.html");
Locator outer = page.locator("#outer");
Locator inner = outer.locator("#inner");
Locator check = page.locator("#check");
JSHandle text = inner.evaluateHandle("e => e.firstChild");
page.evaluate("() => 1"); // Give them a chance to calculate the preview.
assertEquals("Locator@#outer", outer.toString());
assertEquals("Locator@#outer >> #inner", inner.toString());
assertEquals("JSHandle@#text=Text,↵more text", text.toString());
assertEquals("Locator@#check", check.toString());
}
@Test
void getAttributeShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
Locator locator = page.locator("#outer");
assertEquals("value", locator.getAttribute("name"));
assertNull(locator.getAttribute("foo"));
assertEquals("value", page.getAttribute("#outer", "name"));
assertNull(page.getAttribute("#outer", "foo"));
}
@Test
void inputValueShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
page.selectOption("#select", "foo");
assertEquals("foo", page.inputValue("#select"));
page.fill("#textarea", "text value");
assertEquals("text value", page.inputValue("#textarea"));
page.fill("#input", "input value");
assertEquals("input value", page.inputValue("#input"));
Locator locator = page.locator("#input");
assertEquals("input value", locator.inputValue());
try {
page.inputValue("#inner");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement or HTMLSelectElement"));
}
try {
Locator locator2 = page.locator("#inner");
locator2.inputValue();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement or HTMLSelectElement"));
}
}
@Test
void innerHTMLShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
Locator locator = page.locator("#outer");
assertEquals("<div id=\"inner\">Text,\nmore text</div>", locator.innerHTML());
assertEquals("<div id=\"inner\">Text,\nmore text</div>", page.innerHTML("#outer"));
}
@Test
void innerTextShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
Locator locator = page.locator("#inner");
assertEquals("Text, more text", locator.innerText());
assertEquals("Text, more text", page.innerText("#inner"));
}
@Test
void innerTextShouldThrow() {
page.setContent("<svg>text</svg>");
try {
page.innerText("svg");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not an HTMLElement"));
}
Locator locator = page.locator("svg");
try {
locator.innerText();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not an HTMLElement"));
}
}
@Test
void textContentShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
Locator locator = page.locator("#inner");
assertEquals("Text,\nmore text", locator.textContent());
assertEquals("Text,\nmore text", page.textContent("#inner"));
}
@Test
void isVisibleAndIsHiddenShouldWork() {
page.setContent("<div>Hi</div><span></span>");
Locator div = page.locator("div");
assertTrue(div.isVisible());
assertFalse(div.isHidden());
assertTrue(page.isVisible("div"));
assertFalse(page.isHidden("div"));
Locator span = page.locator("span");
assertFalse(span.isVisible());
assertTrue(span.isHidden());
assertFalse(page.isVisible("span"));
assertTrue(page.isHidden("span"));
assertFalse(page.isVisible("no-such-element"));
assertTrue(page.isHidden("no-such-element"));
}
@Test
void isEnabledAndIsDisabledShouldWork() {
page.setContent("<button disabled>button1</button>\n" +
"<button>button2</button>\n" +
"<div>div</div>");
Locator div = page.locator("div");
assertTrue(div.isEnabled());
assertFalse(div.isDisabled());
assertTrue(page.isEnabled("div"));
assertEquals(false, page.isDisabled("div"));
Locator button1 = page.locator(":text('button1')");
assertEquals(false, button1.isEnabled());
assertTrue(button1.isDisabled());
assertEquals(false, page.isEnabled(":text('button1')"));
assertTrue(page.isDisabled(":text('button1')"));
Locator button2 = page.locator(":text('button2')");
assertTrue(button2.isEnabled());
assertEquals(false, button2.isDisabled());
assertTrue(page.isEnabled(":text('button2')"));
assertEquals(false, page.isDisabled(":text('button2')"));
}
@Test
void isEditableShouldWork() {
page.setContent("<input id=input1 disabled><textarea></textarea><input id=input2>");
page.evalOnSelector("textarea", "t => t.readOnly = true");
Locator input1 = page.locator("#input1");
assertFalse(input1.isEditable());
assertFalse(page.isEditable("#input1"));
Locator input2 = page.locator("#input2");
assertTrue(input2.isEditable());
assertTrue(page.isEditable("#input2"));
Locator textarea = page.locator("textarea");
assertFalse(textarea.isEditable());
assertFalse(page.isEditable("textarea"));
}
@Test
void isCheckedShouldWork() {
page.setContent("<input type='checkbox' checked><div>Not a checkbox</div>");
Locator element = page.locator("input");
assertTrue(element.isChecked());
assertTrue(page.isChecked("input"));
element.evaluate("input => input.checked = false");
assertFalse(element.isChecked());
assertFalse(page.isChecked("input"));
try {
page.isChecked("div");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
}
@Test
void allTextContentsShouldWork() {
page.setContent("<div>A</div><div>B</div><div>C</div>");
assertEquals(asList("A", "B", "C"), page.locator("div").allTextContents());
}
@Test
void allInnerTextsShouldWork() {
page.setContent("<div>A</div><div>B</div><div>C</div>");
assertEquals(asList("A", "B", "C"), page.locator("div").allInnerTexts());
}
}
@@ -0,0 +1,61 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestLocatorElementHandle extends TestBase {
@Test
void shouldQueryExistingElement() {
page.navigate(server.PREFIX + "/playground.html");
page.setContent("<html><body><div class='second'><div class='inner'>A</div></div></body></html>");
Locator html = page.locator("html");
Locator second = html.locator(".second");
Locator inner = second.locator(".inner");
Object content = page.evaluate("e => e.textContent", inner.elementHandle());
assertEquals("A", content);
}
@Test
void shouldQueryExistingElements() {
page.setContent("<html><body><div>A</div><br/><div>B</div></body></html>");
Locator html = page.locator("html");
List<ElementHandle> elements = html.locator("div").elementHandles();
assertEquals(2, elements.size());
List<Object> texts = elements.stream().map(element -> page.evaluate("e => e.textContent", element))
.collect(Collectors.toList());
assertEquals(asList("A", "B"), texts);
}
@Test
void shouldReturnEmptyArrayForNonExistingElements() {
page.setContent("<html><body><span>A</span><br/><span>B</span></body></html>");
Locator html = page.locator("html");
List<ElementHandle> elements = html.locator("div").elementHandles();
assertEquals(0, elements.size());
}
@Test
void xpathShouldQueryExistingElement() {
page.navigate(server.PREFIX + "/playground.html");
page.setContent("<html><body><div class='second'><div class='inner'>A</div></div></body></html>");
Locator html = page.locator("html");
Locator second = html.locator("xpath=./body/div[contains(@class, 'second')]");
Locator inner = second.locator("xpath=./div[contains(@class, 'inner')]");
Object content = page.evaluate("e => e.textContent", inner.elementHandle());
assertEquals("A", content);
}
@Test
void xpathShouldReturnNullForNonExistingElement() {
page.setContent("<html><body><div class='second'><div class='inner'>B</div></div></body></html>");
Locator html = page.locator("html");
List<ElementHandle> second = html.locator("xpath=/div[contains(@class, 'third')]").elementHandles();
assertEquals(asList(), second);
}
}
@@ -0,0 +1,51 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestLocatorEvaluate extends TestBase {
@Test
void shouldWork() {
page.setContent("<html><body><div class='tweet'><div class='like'>100</div><div class='retweets'>10</div></div></body></html>");
Locator tweet = page.locator(".tweet .like");
Object content = tweet.evaluate("node => node.innerText");
assertEquals("100", content);
}
@Test
void shouldRetrieveContentFromSubtree() {
String htmlContent = "<div class='a'>not-a-child-div</div><div id='myId'><div class='a'>a-child-div</div></div>";
page.setContent(htmlContent);
Locator locator = page.locator("#myId .a");
Object content = locator.evaluate("node => node.innerText");
assertEquals("a-child-div", content);
}
@Test
void shouldWorkForAll() {
page.setContent("<html><body><div class='tweet'><div class='like'>100</div><div class='like'>10</div></div></body></html>");
Locator tweet = page.locator(".tweet .like");
Object content = tweet.evaluateAll("nodes => nodes.map(n => n.innerText)");
assertEquals(asList("100", "10"), content);
}
@Test
void shouldRetrieveContentFromSubtreeForAll() {
String htmlContent = "<div class='a'>not-a-child-div</div><div id='myId'><div class='a'>a1-child-div</div><div class='a'>a2-child-div</div></div>";
page.setContent(htmlContent);
Locator element = page.locator("#myId .a");
Object content = element.evaluateAll("nodes => nodes.map(n => n.innerText)");
assertEquals(asList("a1-child-div", "a2-child-div"), content);
}
@Test
void shouldNotThrowInCaseOfMissingSelectorForAll() {
String htmlContent = "<div class='a'>not-a-child-div</div><div id='myId'></div>";
page.setContent(htmlContent);
Locator element = page.locator("#myId .a");
Object nodesLength = element.evaluateAll("nodes => nodes.length");
assertEquals(0, nodesLength);
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestLocatorMisc extends TestBase{
@Test
void shouldCheckTheBoxUsingSetChecked() {
page.setContent("<input id='checkbox' type='checkbox'></input>");
Locator input = page.locator("input");
input.setChecked(true);
assertEquals(true, page.evaluate("checkbox.checked"));
input.setChecked(false);
assertEquals(false, page.evaluate("checkbox.checked"));
}
}
@@ -73,6 +73,18 @@ public class TestNetworkRequest extends TestBase {
assertEquals(server.PREFIX + "/empty.html", requests.get(1).url());
}
@Test
void shouldWorkAllHeadersInsideRoute() {
List<Request> requests = new ArrayList<>();
page.route("**", route -> {
assertTrue(route.request().allHeaders().get("accept").length() > 5);
requests.add(route.request());
route.resume();
});
page.navigate(server.PREFIX + "/empty.html");
assertEquals(1, requests.size());
}
// https://github.com/microsoft/playwright/issues/3993
@Test
void shouldNotWorkForARedirectAndInterception() {
@@ -106,8 +118,12 @@ public class TestNetworkRequest extends TestBase {
return (isWebKit() && getOS() == Utils.OS.WINDOWS) || isChromium();
}
static boolean isWebKitWindows() {
return isWebKit() && getOS() == Utils.OS.WINDOWS;
}
@Test
@DisabledIf(value="isWebKitWindowsOrChromium", disabledReason="Flaky, see https://github.com/microsoft/playwright/issues/6690")
@DisabledIf(value="isWebKitWindows", disabledReason="Flaky, see https://github.com/microsoft/playwright/issues/6690")
void shouldGetTheSameHeadersAsTheServer() throws ExecutionException, InterruptedException {
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
server.setRoute("/empty.html", exchange -> {
@@ -119,7 +135,7 @@ public class TestNetworkRequest extends TestBase {
Response response = page.navigate(server.PREFIX + "/empty.html");
Map<String, String> expectedHeaders = serverRequest.get().headers.entrySet().stream().collect(
Collectors.toMap(e -> e.getKey().toLowerCase(), e -> e.getValue().get(0)));
assertEquals(expectedHeaders, response.request().headers());
assertEquals(expectedHeaders, response.request().allHeaders());
}
@Test
@@ -143,7 +159,7 @@ public class TestNetworkRequest extends TestBase {
});
Map<String, String> expectedHeaders = serverRequest.get().headers.entrySet().stream().collect(
Collectors.toMap(e -> e.getKey().toLowerCase(), e -> e.getValue().get(0)));
assertEquals(expectedHeaders, response.request().headers());
assertEquals(expectedHeaders, response.request().allHeaders());
}
@Test
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.ServerAddr;
import org.junit.jupiter.api.Test;
import java.io.IOException;
@@ -26,6 +27,7 @@ import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestNetworkResponse extends TestBase {
@@ -141,4 +143,13 @@ public class TestNetworkResponse extends TestBase {
Response response = page.navigate(server.PREFIX + "/cool");
assertEquals("OK", response.statusText());
}
@Test
void shouldReturnServerAddress() {
Response response = page.navigate(server.EMPTY_PAGE);
ServerAddr address = response.serverAddr();
assertNotNull(address);
assertEquals(server.PORT, address.port);
assertTrue(asList("127.0.0.1", "::1").contains(address.ipAddress), address.ipAddress);
}
}
@@ -39,7 +39,7 @@ public class TestPageBasic extends TestBase {
newPage.evaluate("() => new Promise(r => {})");
fail("evaluate should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Protocol error"));
assertTrue(e.getMessage().contains("Target closed"), e.getMessage());
}
}
@@ -291,4 +291,10 @@ public class TestPageBasic extends TestBase {
}
}
@Test
void frameDragAndDropShouldWork() {
page.navigate(server.PREFIX + "/drag-n-drop.html");
page.dragAndDrop("#source", "#target");
assertEquals(true, page.evalOnSelector("#target", "target => target.contains(document.querySelector('#source'))"));
}
}

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