1
0
mirror of synced 2026-08-07 16:06:55 +00:00

Compare commits

..

84 Commits

Author SHA1 Message Date
Yury Semikhatsky 1432b62b84 chore: update version to 1.9.1-alpha-0 (#339) 2021-03-10 09:54:24 -08:00
Yury Semikhatsky fd87e552f2 chore: roll driver to 1.9.2 (#338) 2021-03-10 09:33:18 -08:00
Yury Semikhatsky 0891edd336 chore: update version to 1.9.0-alpha-0 (#329) 2021-03-05 15:58:56 -08:00
Yury Semikhatsky 10da11cd7c chore: update javadocs to reference set* methods (#328) 2021-03-05 15:17:28 -08:00
Max Schmitt 71b5865d5e docs(readme): use aka.ms Playwright Slack invite link (#327) 2021-03-05 14:38:40 -08:00
Yury Semikhatsky ce54e385c5 docs: add more badges (#326) 2021-03-05 14:32:34 -08:00
Yury Semikhatsky 7d333f994e docs: update examples to use set* instead of with* (#325) 2021-03-05 14:29:41 -08:00
Yury Semikhatsky 617822fa18 fix: rename builder methods from with* to set* (#324) 2021-03-05 13:38:26 -08:00
Yury Semikhatsky 86c06c4fd3 fix(api): throw TimeoutError on timeout (#323) 2021-03-05 13:18:24 -08:00
Yury Semikhatsky d57c449aef test: add basic cli test (#321) 2021-03-05 10:12:45 -08:00
Yury Semikhatsky 9fe7496261 fix: set default codegen language to java (#320) 2021-03-04 22:33:49 -08:00
Yury Semikhatsky 9b568ab4ae feat: add class for launching cli using mvn (#319) 2021-03-04 21:52:12 -08:00
Yury Semikhatsky e9d5d6c5cd fix: correctly terminate connection on remote browser close (#318) 2021-03-04 14:26:22 -08:00
Yury Semikhatsky 2094d40ecb fix: delete deprecated accessibility API, add onceDialog (#317) 2021-03-03 21:15:34 -08:00
Yury Semikhatsky 315bda9b48 feat: provide stacktraces for inspector (#316) 2021-03-03 14:45:35 -08:00
Yury Semikhatsky db5bb8b9c2 fix: use default file system for relative path resolution (#315) 2021-03-03 11:42:17 -08:00
Yury Semikhatsky 6d982db6b9 fix: implement BrowserType.connect (#314) 2021-03-02 18:13:27 -08:00
Yury Semikhatsky 65fdde3e36 docs: use double quotes, linkify Promise (#312) 2021-03-02 12:27:48 -08:00
Yury Semikhatsky 33cbcd6fc7 fix: roll 1.9.1-1614654987000, rename onConsole->onConsoleMessage (#311) 2021-03-01 21:29:12 -08:00
Yury Semikhatsky 5ce2bb6527 docs: generate even documentation (#310) 2021-03-01 17:36:52 -08:00
Yury Semikhatsky 7e3cec5aac docs: patch javadoc links to API members and external sources (#309) 2021-03-01 16:38:29 -08:00
Yury Semikhatsky 606d9947d4 docs: add code snippets from upstream (#308) 2021-03-01 15:56:57 -08:00
Yury Semikhatsky aea9218162 chore: roll driver to 1.9.1-1614633211000 (#307) 2021-03-01 15:04:54 -08:00
Yury Semikhatsky 9885f72a66 docs: add examples from intro (#304) 2021-02-23 13:18:15 -08:00
Yury Semikhatsky d63a8e31c3 fix(logging): use thread-safe time formatter (#303) 2021-02-23 12:52:02 -08:00
Yury Semikhatsky 32afbc065b docs: fix examples to work with the latest api (#302) 2021-02-23 12:30:03 -08:00
Yury Semikhatsky 08d84cf629 chore: harden custom type mapping (#299) 2021-02-19 18:54:06 -08:00
Yury Semikhatsky 85553f691f chore: remove custom generator for cookie methods (#298) 2021-02-19 18:22:23 -08:00
Yury Semikhatsky 2c6aa8f8b3 chore: remove Types.java (#297) 2021-02-19 16:45:57 -08:00
Yury Semikhatsky 80c11f76aa fix: change postData field type to Object (#296) 2021-02-19 16:28:26 -08:00
Yury Semikhatsky aa853e8386 chore: generate selectOption from api.json (#295) 2021-02-19 15:44:10 -08:00
Yury Semikhatsky 99890b57bc fix: generate helper builder methods for nested types (#294) 2021-02-19 14:34:01 -08:00
Yury Semikhatsky ab5cd1c511 fix: generate overloaded builders for options (#293) 2021-02-19 13:46:08 -08:00
Yury Semikhatsky b7a822ee41 chore: always use number for polling interval option (#292) 2021-02-19 12:39:23 -08:00
Yury Semikhatsky c9787e2102 chore: user array instead of list only for overloaded params (#291) 2021-02-19 11:44:58 -08:00
Yury Semikhatsky 8bfb69b93d fix: run tests on release-* branches too (#289) 2021-02-19 09:35:30 -08:00
Yury Semikhatsky 8e62a47f6d fix: generate FilePayload and setInputFiles from api.json (#286) 2021-02-12 18:14:09 -08:00
Yury Semikhatsky b8a774eecc fix: generate frameByUrl from api.json (#285) 2021-02-12 16:23:04 -08:00
Yury Semikhatsky 6fb0b01a17 fix: generate javadocs for all overloads (#284) 2021-02-12 15:15:41 -08:00
Yury Semikhatsky 5fa5a513d2 fix: start generating overloaded method for union params from api.json (#283) 2021-02-12 14:53:47 -08:00
Yury Semikhatsky 5f122784f8 chore: roll cli to 1.9.0-next-1613157126000 (#282) 2021-02-12 12:21:45 -08:00
Yury Semikhatsky f3003bad20 chore: remove custom signatures for non-java methods (#281) 2021-02-12 10:08:11 -08:00
Yury Semikhatsky 0afb42bb0f fix: remove devices from the API (#280) 2021-02-11 12:53:18 -08:00
Yury Semikhatsky 8db59ba9a1 chore: update cli to match current api (#279) 2021-02-11 08:29:53 -08:00
Yury Semikhatsky 5bf883a456 fix: move exposed binding and funcion callbacks to top level (#278) 2021-02-10 18:14:24 -08:00
Yury Semikhatsky d4ef6d6431 chore: simplify generator (#277) 2021-02-10 16:20:33 -08:00
Yury Semikhatsky ea34deeb2b fix: generate return and param types from api.json (#276) 2021-02-10 15:42:03 -08:00
Yury Semikhatsky 442a577506 fix: drop RecordHar and RecordVideo clasess, use flat list of their options (#275) 2021-02-10 12:00:43 -08:00
Yury Semikhatsky fcc1d8672a fix: take required options as constructor params (#274) 2021-02-09 17:58:55 -08:00
Yury Semikhatsky 429f2969aa fix: extract some option classes to top level (#273) 2021-02-09 17:15:09 -08:00
Yury Semikhatsky 7642097291 chore: remove special case for WebSocketFrame (#272) 2021-02-09 16:50:01 -08:00
Yury Semikhatsky bd14c560d2 fix: import options package in the example (#271) 2021-02-09 10:59:19 -08:00
Yury Semikhatsky 18ca3e6ace chore: remove custom mapping for pdf options (#268) 2021-02-08 18:35:27 -08:00
Yury Semikhatsky b77b9b0d20 chore: move enums and second level nested classes into options package (#267) 2021-02-08 18:33:38 -08:00
Yury Semikhatsky c2690f925c fix: allow building persistent context options from device (#266) 2021-02-08 15:52:32 -08:00
Yury Semikhatsky f409965c8e chore: remove unused code from generator (#265) 2021-02-08 15:52:02 -08:00
Yury Semikhatsky 566e0f90e2 fix(api): generate enums from api.json, put them into separate files (#264) 2021-02-08 15:36:01 -08:00
Yury Semikhatsky b98e920efb docs: fix network interception example to work with 0.190.0 (#262) 2021-02-08 09:20:16 -08:00
Yury Semikhatsky 90940b23f3 fix: generate waitFor* methods from upstream docs (#260) 2021-02-04 22:29:55 -08:00
Yury Semikhatsky 2c9fa04a43 tests: stop using waitFor* methods which will be removed (#259) 2021-02-04 20:36:29 -08:00
Yury Semikhatsky eebb223bc3 chore(generator): use case-sensitive event names from upstream (#257) 2021-02-04 11:55:36 -08:00
Yury Semikhatsky f99d6643cd feat: implement Page.pause (#256) 2021-02-04 09:39:44 -08:00
ephung01 37e5de729f test: add new test 'Test Browser Context Viewport' (#255) 2021-02-04 09:23:34 -08:00
Yury Semikhatsky 811ca89fd4 chore: remove custom generator for bodyBytes (#254) 2021-02-03 18:59:42 -08:00
Yury Semikhatsky 6dbebaed5a fix: rename Route.continue_ to Route.resume (#253) 2021-02-03 14:14:35 -08:00
Yury Semikhatsky ee4f8698da fix: auto dismiss dialogs if there are no listeners (#252) 2021-02-03 13:18:01 -08:00
Yury Semikhatsky e31ea3cbe9 fix: change Accessibility.snapshot type to String (#251) 2021-02-03 12:58:38 -08:00
Yury Semikhatsky b60bbc8b88 fix: change storage state type to String (#250) 2021-02-02 18:05:57 -08:00
Yury Semikhatsky a577e62ece chore: delete code for generating custom option names (#249) 2021-02-02 18:05:34 -08:00
Yury Semikhatsky d14d35610e chore: remove custom generator code for Response.finished (#248) 2021-02-02 12:49:20 -08:00
Yury Semikhatsky 301234aa4f chore: roll cli, remove custom ignoreDefaultArgs generator (#247) 2021-02-02 12:15:30 -08:00
Yury Semikhatsky 7a7edea189 fix: add snapshot repo to the examples project (#246) 2021-02-02 11:10:07 -08:00
JB Nizet a6f25595f3 feat: extend AutoCloseable in Playwright, BrowserContext, Browser and… (#238) 2021-02-01 17:31:51 -08:00
Yury Semikhatsky 5af091880f chore: remove more upstreamed stuff (#244) 2021-02-01 13:34:23 -08:00
Yury Semikhatsky 4c165c2069 feat: update driver to 1.9.0-next-1612209901000 (#243) 2021-02-01 12:51:25 -08:00
Yury Semikhatsky 65df7be823 fix: remove Exception from Playwright.close signature (#240) 2021-01-29 21:12:25 -08:00
JB Nizet 2c6e278167 chore: avoid creating a useless DataOutputStream when writing to a file (#236) 2021-01-29 11:14:19 -08:00
JB Nizet 68525d1426 fix: avoid NPE when saving storage state (#235) 2021-01-29 11:13:52 -08:00
Yury Semikhatsky 1da197fdf5 chore: delete Page.LoadState, use Frame.LoadState instead (#234) 2021-01-25 16:00:30 -08:00
Yury Semikhatsky e545829bf5 fix(api): make Dialog.type() return string (#233) 2021-01-25 15:11:05 -08:00
Yury Semikhatsky 982cc6a3bc chore: streamline enum serialization (#232) 2021-01-25 12:35:13 -08:00
Yury Semikhatsky dc139d25d1 chore: delete Event interface (#231) 2021-01-25 11:18:01 -08:00
ephung01 4f9d4f170f test: add new test 'Selectors CSS' (#229) 2021-01-25 11:17:08 -08:00
Yury Semikhatsky 55344516ea chore: move on to 0.190.0-SNAPSHOT (#228) 2021-01-22 18:19:31 -08:00
165 changed files with 11786 additions and 6682 deletions
-2
View File
@@ -1,5 +1,4 @@
name: Test
on:
push:
branches:
@@ -9,7 +8,6 @@ on:
branches:
- master
- release-*
jobs:
build:
timeout-minutes: 30
+30
View File
@@ -0,0 +1,30 @@
name: Test CLI
on:
push:
branches:
- master
- release-*
pull_request:
branches:
- master
- release-*
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
run: scripts/download_driver_for_all_platforms.sh
- name: Intall Playwright
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
+59 -57
View File
@@ -1,6 +1,9 @@
# 🎭 [Playwright](https://playwright.dev) for Java
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
[![javadoc](https://javadoc.io/badge2/com.microsoft.playwright/playwright/javadoc.svg)](https://javadoc.io/doc/com.microsoft.playwright/playwright)
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright)
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.microsoft.playwright/playwright.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack)
#### [Website](https://playwright.dev/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
@@ -8,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->89.0.4344.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->90.0.4392.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->14.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->85.0b1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->85.0b5<!-- 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/#?path=docs/intro.md&q=system-requirements) for details.
@@ -40,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.171.0</version>
<version>0.180.0</version>
</dependency>
```
@@ -64,23 +67,22 @@ import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
List<BrowserType> browserTypes = Arrays.asList(
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
browser.close();
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
}
playwright.close();
}
}
```
@@ -90,27 +92,31 @@ public class PageScreenshot {
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
```java
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withDevice(pixel2)
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
browser.close();
playwright.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
}
```
@@ -123,23 +129,21 @@ This code snippet navigates to example.com in Firefox, and executes a script in
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.firefox();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.firefox().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
browser.close();
playwright.close();
System.out.println(dimensions);
}
}
}
```
@@ -152,19 +156,17 @@ This code snippet sets up request routing for a WebKit page to log all network r
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
}
}
```
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.181.0</version>
<version>1.9.1-alpha-0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -61,8 +61,13 @@ public class DriverJar extends Driver {
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
Path srcRoot = Paths.get(uri);
// jar file system's .relativize gives wrong results when used with
// spring-boot-maven-plugin, convert to the default filesystem to
// have predictable results.
// See https://github.com/microsoft/playwright-java/issues/306
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
Files.walk(srcRoot).forEach(fromPath -> {
Path relative = srcRoot.relativize(fromPath);
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
Path toPath = driverTempDir.resolve(relative.toString());
try {
if (Files.isDirectory(fromPath)) {
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.181.0</version>
<version>1.9.1-alpha-0</version>
</parent>
<artifactId>driver</artifactId>
+9 -1
View File
@@ -15,7 +15,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.180.0-SNAPSHOT</version>
<version>0.190.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
@@ -31,4 +31,12 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
</project>
@@ -19,22 +19,20 @@ package org.example;
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.firefox();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
browser.close();
playwright.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.firefox().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
}
}
}
@@ -19,18 +19,16 @@ package org.example;
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
}
}
@@ -16,6 +16,7 @@
package org.example;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
@@ -23,21 +24,22 @@ import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withDevice(pixel2)
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
browser.close();
playwright.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
}
@@ -23,22 +23,21 @@ import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
browser.close();
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
}
playwright.close();
}
}
@@ -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 org.example;
import com.microsoft.playwright.*;
public class PrintTitle {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.navigate("http://playwright.dev");
System.out.println(page.title());
}
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class WebKitScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
Page page = browser.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
}
}
}
+5 -6
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.181.0</version>
<version>1.9.1-alpha-0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -61,6 +61,10 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
@@ -73,10 +77,5 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,68 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.*;
/**
* The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by
* assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or
* [switches](https://en.wikipedia.org/wiki/Switch_access).
*
* <p> Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
* have wildly different output.
*
* <p> Rendering engines of Chromium, Firefox and Webkit have a concept of "accessibility tree", which is then translated into
* different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
*
* <p> Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
* AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing
* only the "interesting" nodes of the tree.
*/
public interface Accessibility {
class SnapshotOptions {
/**
* Prune uninteresting nodes from the tree. Defaults to {@code true}.
*/
public Boolean interestingOnly;
/**
* The root DOM element for the snapshot. Defaults to the whole page.
*/
public ElementHandle root;
public SnapshotOptions withInterestingOnly(boolean interestingOnly) {
this.interestingOnly = interestingOnly;
return this;
}
public SnapshotOptions withRoot(ElementHandle root) {
this.root = root;
return this;
}
}
default AccessibilityNode snapshot() {
return snapshot(null);
}
/**
* Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
* page.
*
* <p> <strong>NOTE:</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
* Playwright will discard them as well for an easier to process tree, unless {@code interestingOnly} is set to {@code false}.
*/
AccessibilityNode snapshot(SnapshotOptions options);
}
@@ -1,51 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.List;
public interface AccessibilityNode {
String role();
String name();
String valueString();
Double valueNumber();
String description();
String keyshortcuts();
String roledescription();
String valuetext();
Boolean disabled();
Boolean expanded();
Boolean focused();
Boolean modal();
Boolean multiline();
Boolean multiselectable();
Boolean readonly();
Boolean required();
Boolean selected();
enum CheckedState { CHECKED, UNCHECKED, MIXED }
CheckedState checked();
enum PressedState { PRESSED, RELEASED, MIXED }
PressedState pressed();
Integer level();
Double valuemin();
Double valuemax();
String autocomplete();
String haspopup();
String invalid();
String orientation();
List<AccessibilityNode> children();
}
@@ -16,134 +16,46 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
/**
* - extends: [EventEmitter]
* A Browser is created via {@link BrowserType#launch BrowserType.launch()}. An example of using a {@code Browser} to create a
* {@code Page}:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* <p> A Browser is created via [{@code method: BrowserType.launch}]. An example of using a {@code Browser} to create a [Page]:
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox()
* Browser browser = firefox.launch();
* Page page = browser.newPage();
* page.navigate('https://example.com');
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface Browser {
class VideoSize {
private final int width;
private final int height;
public VideoSize(int width, int height) {
this.width = width;
this.height = height;
}
public int width() {
return width;
}
public int height() {
return height;
}
}
public interface Browser extends AutoCloseable {
/**
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following:
* <ul>
* <li> Browser application is closed or crashed.</li>
* <li> The {@link Browser#close Browser.close()} method was called.</li>
* </ul>
*/
void onDisconnected(Consumer<Browser> handler);
/**
* Removes handler that was previously added with {@link #onDisconnected onDisconnected(handler)}.
*/
void offDisconnected(Consumer<Browser> handler);
class NewContextOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public VideoSize size;
RecordVideo() {
}
public NewContextOptions done() {
return NewContextOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(int width, int height) {
this.size = new VideoSize(width, height);
return this;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
@@ -153,8 +65,8 @@ public interface Browser {
*/
public Boolean bypassCSP;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See
* [{@code method: Page.emulateMedia}] for more details. Defaults to '{@code light}'.
* 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 ColorScheme colorScheme;
/**
@@ -171,9 +83,9 @@ public interface Browser {
*/
public Boolean hasTouch;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
@@ -197,8 +109,8 @@ public interface Browser {
*/
public Boolean offline;
/**
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public List<String> permissions;
/**
@@ -208,26 +120,38 @@ public interface Browser {
*/
public Proxy proxy;
/**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into {@code recordHar.path} file. If not
* specified, the HAR is not recorded. Make sure to await [{@code method: BrowserContext.close}] for the HAR to be saved.
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public RecordHar recordHar;
public Boolean recordHarOmitContent;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make
* sure to await [{@code method: BrowserContext.close}] for videos to be saved.
* Path on the filesystem to write the HAR file to.
*/
public RecordVideo recordVideo;
public Path recordHarPath;
/**
* Populates context with given storage state. This method can be used to initialize context with logged-in information
* obtained via [{@code method: BrowserContext.storageState}]. Either a path to the file with saved storage, or an object with
* the following fields:
* Path to the directory to put videos into.
*/
public Path recordVideoDir;
/**
* 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 RecordVideoSize recordVideoSize;
/**
* 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 String storageState;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public BrowserContext.StorageState storageState;
public Path storageStatePath;
/**
* Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
* for a list of supported timezone IDs.
* 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 String timezoneId;
/**
@@ -237,202 +161,121 @@ public interface Browser {
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public Optional<ViewportSize> viewportSize;
public NewContextOptions withAcceptDownloads(boolean acceptDownloads) {
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public NewContextOptions withBypassCSP(boolean bypassCSP) {
public NewContextOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public NewContextOptions withColorScheme(ColorScheme colorScheme) {
public NewContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public NewContextOptions withDeviceScaleFactor(double deviceScaleFactor) {
public NewContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewContextOptions withGeolocation(Geolocation geolocation) {
public NewContextOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
public NewContextOptions setGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewContextOptions withHasTouch(boolean hasTouch) {
public NewContextOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
public NewContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
public NewContextOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewContextOptions withIsMobile(boolean isMobile) {
public NewContextOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewContextOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
public NewContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewContextOptions withLocale(String locale) {
public NewContextOptions setLocale(String locale) {
this.locale = locale;
return this;
}
public NewContextOptions withOffline(boolean offline) {
public NewContextOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
public NewContextOptions withPermissions(List<String> permissions) {
public NewContextOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
}
public NewContextOptions withStorageState(BrowserContext.StorageState storageState) {
this.storageState = storageState;
this.storageStatePath = null;
public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public NewContextOptions withStorageState(Path storageStatePath) {
this.storageState = null;
public NewContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
public NewContextOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
public NewContextOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
public NewContextOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
public NewContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
public NewContextOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
}
public NewContextOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
public NewContextOptions withTimezoneId(String timezoneId) {
public NewContextOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewContextOptions withUserAgent(String userAgent) {
public NewContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public NewContextOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
public NewContextOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
public NewContextOptions withDevice(DeviceDescriptor device) {
withViewport(device.viewport().width(), device.viewport().height());
withUserAgent(device.userAgent());
withDeviceScaleFactor(device.deviceScaleFactor());
withIsMobile(device.isMobile());
withHasTouch(device.hasTouch());
public NewContextOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
}
}
class NewPageOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public VideoSize size;
RecordVideo() {
}
public NewPageOptions done() {
return NewPageOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(int width, int height) {
this.size = new VideoSize(width, height);
return this;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
@@ -442,8 +285,8 @@ public interface Browser {
*/
public Boolean bypassCSP;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See
* [{@code method: Page.emulateMedia}] for more details. Defaults to '{@code light}'.
* 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 ColorScheme colorScheme;
/**
@@ -460,9 +303,9 @@ public interface Browser {
*/
public Boolean hasTouch;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
@@ -486,8 +329,8 @@ public interface Browser {
*/
public Boolean offline;
/**
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public List<String> permissions;
/**
@@ -497,26 +340,38 @@ public interface Browser {
*/
public Proxy proxy;
/**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into {@code recordHar.path} file. If not
* specified, the HAR is not recorded. Make sure to await [{@code method: BrowserContext.close}] for the HAR to be saved.
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public RecordHar recordHar;
public Boolean recordHarOmitContent;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make
* sure to await [{@code method: BrowserContext.close}] for videos to be saved.
* Path on the filesystem to write the HAR file to.
*/
public RecordVideo recordVideo;
public Path recordHarPath;
/**
* Populates context with given storage state. This method can be used to initialize context with logged-in information
* obtained via [{@code method: BrowserContext.storageState}]. Either a path to the file with saved storage, or an object with
* the following fields:
* Path to the directory to put videos into.
*/
public Path recordVideoDir;
/**
* 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 RecordVideoSize recordVideoSize;
/**
* 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 String storageState;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public BrowserContext.StorageState storageState;
public Path storageStatePath;
/**
* Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
* for a list of supported timezone IDs.
* 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 String timezoneId;
/**
@@ -526,110 +381,123 @@ public interface Browser {
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public Optional<ViewportSize> viewportSize;
public NewPageOptions withAcceptDownloads(boolean acceptDownloads) {
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public NewPageOptions withBypassCSP(boolean bypassCSP) {
public NewPageOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public NewPageOptions withColorScheme(ColorScheme colorScheme) {
public NewPageOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public NewPageOptions withDeviceScaleFactor(double deviceScaleFactor) {
public NewPageOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewPageOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
public NewPageOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewPageOptions withGeolocation(Geolocation geolocation) {
public NewPageOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
public NewPageOptions setGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewPageOptions withHasTouch(boolean hasTouch) {
public NewPageOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewPageOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
public NewPageOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
public NewPageOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
public NewPageOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
public NewPageOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewPageOptions withIsMobile(boolean isMobile) {
public NewPageOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewPageOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
public NewPageOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewPageOptions withLocale(String locale) {
public NewPageOptions setLocale(String locale) {
this.locale = locale;
return this;
}
public NewPageOptions withOffline(boolean offline) {
public NewPageOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
public NewPageOptions withPermissions(List<String> permissions) {
public NewPageOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public NewPageOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
}
public NewPageOptions withStorageState(BrowserContext.StorageState storageState) {
this.storageState = storageState;
this.storageStatePath = null;
public NewPageOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public NewPageOptions withStorageState(Path storageStatePath) {
this.storageState = null;
public NewPageOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
public NewPageOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
public NewPageOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
public NewPageOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
public NewPageOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
public NewPageOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
}
public NewPageOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
public NewPageOptions withTimezoneId(String timezoneId) {
public NewPageOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewPageOptions withUserAgent(String userAgent) {
public NewPageOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public NewPageOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
public NewPageOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
public NewPageOptions withDevice(DeviceDescriptor device) {
withViewport(device.viewport().width(), device.viewport().height());
withUserAgent(device.userAgent());
withDeviceScaleFactor(device.deviceScaleFactor());
withIsMobile(device.isMobile());
withHasTouch(device.hasTouch());
public NewPageOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
}
}
/**
* In case this browser is obtained using [{@code method: BrowserType.launch}], closes the browser and all of its pages (if any
* were opened).
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
*
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
@@ -639,19 +507,51 @@ public interface Browser {
void close();
/**
* Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
* <pre>{@code
* Browser browser = pw.webkit().launch();
* System.out.println(browser.contexts().size()); // prints "0"
* BrowserContext context = browser.newContext();
* System.out.println(browser.contexts().size()); // prints "1"
* }</pre>
*/
List<BrowserContext> contexts();
/**
* Indicates that the browser is connected.
*/
boolean isConnected();
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
* }</pre>
*/
default BrowserContext newContext() {
return newContext(null);
}
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
* <pre>{@code
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
* // Create a new incognito browser context.
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
* }</pre>
*/
BrowserContext newContext(NewContextOptions options);
/**
* Creates a new page in a new browser context. Closing this page will close the context as well.
*
* <p> This is a convenience API that should only be used for the single-page scenarios and short snippets. Production code and
* testing frameworks should explicitly create {@link Browser#newContext Browser.newContext()} followed by the {@link
* BrowserContext#newPage BrowserContext.newPage()} to control their exact life times.
*/
default Page newPage() {
return newPage(null);
}
@@ -659,8 +559,8 @@ public interface Browser {
* Creates a new page in a new browser context. Closing this page will close the context as well.
*
* <p> This is a convenience API that should only be used for the single-page scenarios and short snippets. Production code and
* testing frameworks should explicitly create [{@code method: Browser.newContext}] followed by the
* [{@code method: BrowserContext.newPage}] to control their exact life times.
* testing frameworks should explicitly create {@link Browser#newContext Browser.newContext()} followed by the {@link
* BrowserContext#newPage BrowserContext.newPage()} to control their exact life times.
*/
Page newPage(NewPageOptions options);
/**
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
@@ -23,202 +24,63 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
/**
* - extends: [EventEmitter]
*
* <p> BrowserContexts provide a way to operate multiple independent browser sessions.
* BrowserContexts provide a way to operate multiple independent browser sessions.
*
* <p> If a page opens another page, e.g. with a {@code window.open} call, the popup will belong to the parent page's browser
* context.
*
* <p> Playwright allows creation of "incognito" browser contexts with {@code browser.newContext()} method. "Incognito" browser
* contexts don't write any browsing data to disk.
* <pre>{@code
* // Create a new incognito browser context
* BrowserContext context = browser.newContext();
* // Create a new page inside context.
* Page page = context.newPage();
* page.navigate("https://example.com");
* // Dispose context once it"s no longer needed.
* context.close();
* }</pre>
*/
public interface BrowserContext {
enum SameSite { STRICT, LAX, NONE }
class HTTPCredentials {
private final String username;
private final String password;
public HTTPCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public String username() {
return username;
}
public String password() {
return password;
}
}
class StorageState {
public List<AddCookie> cookies;
public List<OriginState> origins;
public static class OriginState {
public final String origin;
public List<LocalStorageItem> localStorage;
public static class LocalStorageItem {
public String name;
public String value;
public LocalStorageItem(String name, String value) {
this.name = name;
this.value = value;
}
}
public OriginState(String origin) {
this.origin = origin;
}
public OriginState withLocalStorage(List<LocalStorageItem> localStorage) {
this.localStorage = localStorage;
return this;
}
}
public StorageState() {
cookies = new ArrayList<>();
origins = new ArrayList<>();
}
public List<AddCookie> cookies() {
return this.cookies;
}
public List<OriginState> origins() {
return this.origins;
}
}
public interface BrowserContext extends AutoCloseable {
/**
* Emitted when Browser context gets closed. This might happen because of one of the following:
* <ul>
* <li> Browser context is closed.</li>
* <li> Browser application is closed or crashed.</li>
* <li> The {@link Browser#close Browser.close()} method was called.</li>
* </ul>
*/
void onClose(Consumer<BrowserContext> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<BrowserContext> handler);
/**
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will
* also fire for popup pages. See also {@link Page#onPopup Page.onPopup()} to receive events about popups relevant to a
* specific page.
*
* <p> The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a
* popup with {@code window.open('http://example.com')}, this event will fire when the network request to "http://example.com" is
* done and its response has started loading in the popup.
* <pre>{@code
* Page newPage = context.waitForPage(() -> {
* page.click("a[target=_blank]");
* });
* System.out.println(newPage.evaluate("location.href"));
* }</pre>
*
* <p> <strong>NOTE:</strong> Use {@link Page#waitForLoadState Page.waitForLoadState()} to wait until the page gets to a particular state (you should
* not need it in most cases).
*/
void onPage(Consumer<Page> handler);
/**
* Removes handler that was previously added with {@link #onPage onPage(handler)}.
*/
void offPage(Consumer<Page> handler);
class WaitForPageOptions {
public Double timeout;
public WaitForPageOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Page waitForPage(Runnable code, WaitForPageOptions options);
default Page waitForPage(Runnable code) { return waitForPage(code, null); }
class AddCookie {
public String name;
public String value;
/**
* either url or domain / path are required. Optional.
*/
public String url;
/**
* either url or domain / path are required Optional.
*/
public String domain;
/**
* either url or domain / path are required Optional.
*/
public String path;
/**
* Unix time in seconds. Optional.
*/
public Double expires;
/**
* Optional.
*/
public Boolean httpOnly;
/**
* Optional.
*/
public Boolean secure;
/**
* Optional.
*/
public SameSite sameSite;
public AddCookie withName(String name) {
this.name = name;
return this;
}
public AddCookie withValue(String value) {
this.value = value;
return this;
}
public AddCookie withUrl(String url) {
this.url = url;
return this;
}
public AddCookie withDomain(String domain) {
this.domain = domain;
return this;
}
public AddCookie withPath(String path) {
this.path = path;
return this;
}
public AddCookie withExpires(double expires) {
this.expires = expires;
return this;
}
public AddCookie withHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
public AddCookie withSecure(boolean secure) {
this.secure = secure;
return this;
}
public AddCookie withSameSite(SameSite sameSite) {
this.sameSite = sameSite;
return this;
}
}
class Cookie {
private String name;
private String value;
private String domain;
private String path;
/**
* Unix time in seconds.
*/
private double expires;
private boolean httpOnly;
private boolean secure;
private SameSite sameSite;
public String name() {
return this.name;
}
public String value() {
return this.value;
}
public String domain() {
return this.domain;
}
public String path() {
return this.path;
}
public double expires() {
return this.expires;
}
public boolean httpOnly() {
return this.httpOnly;
}
public boolean secure() {
return this.secure;
}
public SameSite sameSite() {
return this.sameSite;
}
}
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@@ -226,7 +88,7 @@ public interface BrowserContext {
*/
public Boolean handle;
public ExposeBindingOptions withHandle(boolean handle) {
public ExposeBindingOptions setHandle(boolean handle) {
this.handle = handle;
return this;
}
@@ -237,7 +99,7 @@ public interface BrowserContext {
*/
public String origin;
public GrantPermissionsOptions withOrigin(String origin) {
public GrantPermissionsOptions setOrigin(String origin) {
this.origin = origin;
return this;
}
@@ -249,35 +111,85 @@ public interface BrowserContext {
*/
public Path path;
public StorageStateOptions withPath(Path path) {
public StorageStateOptions setPath(Path path) {
this.path = path;
return this;
}
}
class WaitForPageOptions {
/**
* Receives the {@code Page} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<Page> predicate;
/**
* 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 Double timeout;
public WaitForPageOptions setPredicate(Predicate<Page> predicate) {
this.predicate = predicate;
return this;
}
public WaitForPageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be
* obtained via [{@code method: BrowserContext.cookies}].
* obtained via {@link BrowserContext#cookies BrowserContext.cookies()}.
* <pre>{@code
* browserContext.addCookies(Arrays.asList(cookieObject1, cookieObject2));
* }</pre>
*/
void addCookies(List<AddCookie> cookies);
default void addInitScript(String script) {
addInitScript(script, null);
}
void addCookies(List<Cookie> cookies);
/**
* Adds a script which would be evaluated in one of the following scenarios:
* - Whenever a page is created in the browser context or is navigated.
* - Whenever a child frame is attached or navigated in any page in the browser context. In this case, the script is
* evaluated in the context of the newly attached frame.
* <ul>
* <li> Whenever a page is created in the browser context or is navigated.</li>
* <li> Whenever a child frame is attached or navigated in any page in the browser context. In this case, the script is
* evaluated in the context of the newly attached frame.</li>
* </ul>
*
* <p> The script is evaluated after the document was created but before any of its scripts were run. This is useful to amend
* the JavaScript environment, e.g. to seed {@code Math.random}.
*
* <p> <strong>NOTE:</strong> The order of evaluation of multiple scripts installed via [{@code method: BrowserContext.addInitScript}] and
* [{@code method: Page.addInitScript}] is not defined.
* <p> An example of overriding {@code Math.random} before the page loads:
* <pre>{@code
* // In your playwright script, assuming the preload.js file is in same directory.
* browserContext.addInitScript(Paths.get("preload.js"));
* }</pre>
*
* <p> <strong>NOTE:</strong> The order of evaluation of multiple scripts installed via {@link BrowserContext#addInitScript
* BrowserContext.addInitScript()} and {@link Page#addInitScript Page.addInitScript()} is not defined.
*
* @param script Script to be evaluated in all pages in the browser context.
* @param arg Optional argument to pass to {@code script} (only supported when passing a function).
*/
void addInitScript(String script, Object arg);
void addInitScript(String script);
/**
* Adds a script which would be evaluated in one of the following scenarios:
* <ul>
* <li> Whenever a page is created in the browser context or is navigated.</li>
* <li> Whenever a child frame is attached or navigated in any page in the browser context. In this case, the script is
* evaluated in the context of the newly attached frame.</li>
* </ul>
*
* <p> The script is evaluated after the document was created but before any of its scripts were run. This is useful to amend
* the JavaScript environment, e.g. to seed {@code Math.random}.
*
* <p> An example of overriding {@code Math.random} before the page loads:
* <pre>{@code
* // In your playwright script, assuming the preload.js file is in same directory.
* browserContext.addInitScript(Paths.get("preload.js"));
* }</pre>
*
* <p> <strong>NOTE:</strong> The order of evaluation of multiple scripts installed via {@link BrowserContext#addInitScript
* BrowserContext.addInitScript()} and {@link Page#addInitScript Page.addInitScript()} is not defined.
*
* @param script Script to be evaluated in all pages in the browser context.
*/
void addInitScript(Path script);
/**
* Returns the browser instance of the context. If it was launched as a persistent context null gets returned.
*/
@@ -288,6 +200,12 @@ public interface BrowserContext {
void clearCookies();
/**
* Clears all permission overrides for the browser context.
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.grantPermissions(Arrays.asList("clipboard-read"));
* // do stuff ..
* context.clearPermissions();
* }</pre>
*/
void clearPermissions();
/**
@@ -296,8 +214,20 @@ public interface BrowserContext {
* <p> <strong>NOTE:</strong> The default browser context cannot be closed.
*/
void close();
default List<Cookie> cookies() { return cookies((List<String>) null); }
default List<Cookie> cookies(String url) { return cookies(Arrays.asList(url)); }
/**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs
* are returned.
*/
default List<Cookie> cookies() {
return cookies((String) null);
}
/**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs
* are returned.
*
* @param urls Optional list of URLs.
*/
List<Cookie> cookies(String urls);
/**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs
* are returned.
@@ -305,37 +235,201 @@ public interface BrowserContext {
* @param urls Optional list of URLs.
*/
List<Cookie> cookies(List<String> urls);
default void exposeBinding(String name, Page.Binding callback) {
/**
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context. When
* called, the function executes {@code callback} and returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
* resolves to the return value of {@code callback}. If the {@code callback} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
* awaited.
*
* <p> The first argument of the {@code callback} function contains information about the caller: {@code { browserContext: BrowserContext,
* page: Page, frame: Frame }}.
*
* <p> See {@link Page#exposeBinding Page.exposeBinding()} for page-only version.
*
* <p> An example of exposing page URL to all frames in all pages in the context:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
* Page page = context.newPage();
* page.setContent("<script>\n" +
* " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.pageURL();\n" +
* " }\n" +
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.click("button");
* }
* }
* }
* }</pre>
*
* <p> An example of passing an element handle:
* <pre>{@code
* context.exposeBinding("clicked", (source, args) -> {
* ElementHandle element = (ElementHandle) args[0];
* System.out.println(element.textContent());
* return null;
* }, new BrowserContext.ExposeBindingOptions().setHandle(true));
* page.setContent("" +
* "<script>\n" +
* " document.addEventListener('click', event => window.clicked(event.target));\n" +
* "</script>\n" +
* "<div>Click me</div>\n" +
* "<div>Or click me</div>\n");
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
*/
default void exposeBinding(String name, BindingCallback callback) {
exposeBinding(name, callback, null);
}
/**
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context. When
* called, the function executes {@code callback} and returns a [Promise] which resolves to the return value of {@code callback}. If
* the {@code callback} returns a [Promise], it will be awaited.
* called, the function executes {@code callback} and returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
* resolves to the return value of {@code callback}. If the {@code callback} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
* awaited.
*
* <p> The first argument of the {@code callback} function contains information about the caller: `{ browserContext: BrowserContext,
* page: Page, frame: Frame }`.
* <p> The first argument of the {@code callback} function contains information about the caller: {@code { browserContext: BrowserContext,
* page: Page, frame: Frame }}.
*
* <p> See [{@code method: Page.exposeBinding}] for page-only version.
* <p> See {@link Page#exposeBinding Page.exposeBinding()} for page-only version.
*
* <p> An example of exposing page URL to all frames in all pages in the context:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
* Page page = context.newPage();
* page.setContent("<script>\n" +
* " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.pageURL();\n" +
* " }\n" +
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.click("button");
* }
* }
* }
* }</pre>
*
* <p> An example of passing an element handle:
* <pre>{@code
* context.exposeBinding("clicked", (source, args) -> {
* ElementHandle element = (ElementHandle) args[0];
* System.out.println(element.textContent());
* return null;
* }, new BrowserContext.ExposeBindingOptions().setHandle(true));
* page.setContent("" +
* "<script>\n" +
* " document.addEventListener('click', event => window.clicked(event.target));\n" +
* "</script>\n" +
* "<div>Click me</div>\n" +
* "<div>Or click me</div>\n");
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
*/
void exposeBinding(String name, Page.Binding callback, ExposeBindingOptions options);
void exposeBinding(String name, BindingCallback callback, ExposeBindingOptions options);
/**
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context. When
* called, the function executes {@code callback} and returns a [Promise] which resolves to the return value of {@code callback}.
* called, the function executes {@code callback} and returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
* resolves to the return value of {@code callback}.
*
* <p> If the {@code callback} returns a [Promise], it will be awaited.
* <p> If the {@code callback} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
* awaited.
*
* <p> See [{@code method: Page.exposeFunction}] for page-only version.
* <p> See {@link Page#exposeFunction Page.exposeFunction()} for page-only version.
*
* <p> An example of adding an {@code md5} function to all pages in the context:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* import java.nio.charset.StandardCharsets;
* import java.security.MessageDigest;
* import java.security.NoSuchAlgorithmException;
* import java.util.Base64;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* context.exposeFunction("sha1", args -> {
* String text = (String) args[0];
* MessageDigest crypto;
* try {
* crypto = MessageDigest.getInstance("SHA-1");
* } catch (NoSuchAlgorithmException e) {
* return null;
* }
* byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
* return Base64.getEncoder().encodeToString(token);
* });
* Page page = context.newPage();
* page.setContent("<script>\n" +
* " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.sha1('PLAYWRIGHT');\n" +
* " }\n" +
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>\n");
* page.click("button");
* }
* }
* }
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
*/
void exposeFunction(String name, Page.Function callback);
void exposeFunction(String name, FunctionCallback callback);
/**
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified.
*
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* <ul>
* <li> {@code "geolocation"}</li>
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "accelerometer"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "payment-handler"}</li>
* </ul>
*/
default void grantPermissions(List<String> permissions) {
grantPermissions(permissions, null);
}
@@ -344,22 +438,24 @@ public interface BrowserContext {
* specified.
*
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* - {@code 'geolocation'}
* - {@code 'midi'}
* - {@code 'midi-sysex'} (system-exclusive midi)
* - {@code 'notifications'}
* - {@code 'push'}
* - {@code 'camera'}
* - {@code 'microphone'}
* - {@code 'background-sync'}
* - {@code 'ambient-light-sensor'}
* - {@code 'accelerometer'}
* - {@code 'gyroscope'}
* - {@code 'magnetometer'}
* - {@code 'accessibility-events'}
* - {@code 'clipboard-read'}
* - {@code 'clipboard-write'}
* - {@code 'payment-handler'}
* <ul>
* <li> {@code "geolocation"}</li>
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "push"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "accelerometer"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "payment-handler"}</li>
* </ul>
*/
void grantPermissions(List<String> permissions, GrantPermissionsOptions options);
/**
@@ -367,20 +463,95 @@ public interface BrowserContext {
*/
Page newPage();
/**
* Returns all open pages in the context. Non visible pages, such as {@code "background_page"}, will not be listed here. You can
* find them using [{@code method: ChromiumBrowserContext.backgroundPages}].
* Returns all open pages in the context.
*/
List<Page> pages();
/**
* 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> An example of a naïve 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> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <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 handler handler function to route the request.
*/
void route(String url, Consumer<Route> handler);
/**
* 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> An example of a naïve 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> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <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 handler handler function to route the request.
*/
void route(Pattern url, Consumer<Route> handler);
/**
* 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> or the same snippet using a regex pattern instead:
* <p> An example of a naïve 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> Page routes (set up with [{@code method: Page.route}]) take precedence over browser context routes when request matches both
* handlers.
* <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> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
@@ -390,15 +561,18 @@ public interface BrowserContext {
void route(Predicate<String> url, Consumer<Route> handler);
/**
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
* - [{@code method: Page.goBack}]
* - [{@code method: Page.goForward}]
* - [{@code method: Page.goto}]
* - [{@code method: Page.reload}]
* - [{@code method: Page.setContent}]
* - [{@code method: Page.waitForNavigation}]
* <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#reload Page.reload()}</li>
* <li> {@link Page#setContent Page.setContent()}</li>
* <li> {@link Page#waitForNavigation Page.waitForNavigation()}</li>
* </ul>
*
* <p> <strong>NOTE:</strong> [{@code method: Page.setDefaultNavigationTimeout}] and [{@code method: Page.setDefaultTimeout}] take priority over
* [{@code method: BrowserContext.setDefaultNavigationTimeout}].
* <p> <strong>NOTE:</strong> {@link Page#setDefaultNavigationTimeout Page.setDefaultNavigationTimeout()} and {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} take priority over {@link BrowserContext#setDefaultNavigationTimeout
* BrowserContext.setDefaultNavigationTimeout()}.
*
* @param timeout Maximum navigation time in milliseconds
*/
@@ -406,27 +580,33 @@ public interface BrowserContext {
/**
* This setting will change the default maximum time for all the methods accepting {@code timeout} option.
*
* <p> <strong>NOTE:</strong> [{@code method: Page.setDefaultNavigationTimeout}], [{@code method: Page.setDefaultTimeout}] and
* [{@code method: BrowserContext.setDefaultNavigationTimeout}] take priority over [{@code method: BrowserContext.setDefaultTimeout}].
* <p> <strong>NOTE:</strong> {@link Page#setDefaultNavigationTimeout Page.setDefaultNavigationTimeout()}, {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} and {@link BrowserContext#setDefaultNavigationTimeout
* BrowserContext.setDefaultNavigationTimeout()} take priority over {@link BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*
* @param timeout Maximum time in milliseconds
*/
void setDefaultTimeout(double timeout);
/**
* The extra HTTP headers will be sent with every request initiated by any page in the context. These headers are merged
* with page-specific extra HTTP headers set with [{@code method: Page.setExtraHTTPHeaders}]. If page overrides a particular
* header, page-specific header value will be used instead of the browser context header value.
* with page-specific extra HTTP headers set with {@link Page#setExtraHTTPHeaders Page.setExtraHTTPHeaders()}. If page
* overrides a particular header, page-specific header value will be used instead of the browser context header value.
*
* <p> <strong>NOTE:</strong> [{@code method: BrowserContext.setExtraHTTPHeaders}] does not guarantee the order of headers in the outgoing requests.
* <p> <strong>NOTE:</strong> {@link BrowserContext#setExtraHTTPHeaders BrowserContext.setExtraHTTPHeaders()} does not guarantee the order of headers
* in the outgoing requests.
*
* @param headers An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
void setExtraHTTPHeaders(Map<String, String> headers);
/**
* Sets the context's geolocation. Passing {@code null} or {@code undefined} emulates position unavailable.
* <pre>{@code
* browserContext.setGeolocation(new Geolocation(59.95, 30.31667));
* }</pre>
*
* <p> <strong>NOTE:</strong> Consider using [{@code method: BrowserContext.grantPermissions}] to grant permissions for the browser context pages to
* read its geolocation.
* <p> <strong>NOTE:</strong> Consider using {@link BrowserContext#grantPermissions BrowserContext.grantPermissions()} to grant permissions for the
* browser context pages to read its geolocation.
*/
void setGeolocation(Geolocation geolocation);
/**
@@ -435,26 +615,90 @@ public interface BrowserContext {
* @param offline Whether to emulate network being offline for the browser context.
*/
void setOffline(boolean offline);
default StorageState storageState() {
/**
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*/
default String storageState() {
return storageState(null);
}
/**
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*/
StorageState storageState(StorageStateOptions options);
default void unroute(String url) { unroute(url, null); }
default void unroute(Pattern url) { unroute(url, null); }
default void unroute(Predicate<String> url) { unroute(url, null); }
String storageState(StorageStateOptions options);
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
*/
default void unroute(String url) {
unroute(url, null);
}
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link BrowserContext#route BrowserContext.route()}.
*/
void unroute(String url, Consumer<Route> handler);
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
*/
default void unroute(Pattern url) {
unroute(url, null);
}
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link BrowserContext#route BrowserContext.route()}.
*/
void unroute(Pattern url, Consumer<Route> handler);
/**
* Removes a route created with [{@code method: BrowserContext.route}]. When {@code handler} is not specified, removes all routes for
* the {@code url}.
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with
* [{@code method: BrowserContext.route}].
* @param handler Optional handler function used to register a routing with [{@code method: BrowserContext.route}].
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
*/
default void unroute(Predicate<String> url) {
unroute(url, null);
}
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link BrowserContext#route
* BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link BrowserContext#route BrowserContext.route()}.
*/
void unroute(Predicate<String> url, Consumer<Route> handler);
/**
* Performs action and waits for a new {@code Page} to be created in the context. If predicate is provided, it passes {@code Page}
* value into the {@code predicate} function and waits for {@code predicate(event)} to return a truthy value. Will throw an error if
* the context closes before new {@code Page} is created.
*
* @param callback Callback that performs the action triggering the event.
*/
default Page waitForPage(Runnable callback) {
return waitForPage(null, callback);
}
/**
* Performs action and waits for a new {@code Page} to be created in the context. If predicate is provided, it passes {@code Page}
* value into the {@code predicate} function and waits for {@code predicate(event)} to return a truthy value. Will throw an error if
* the context closes before new {@code Page} is created.
*
* @param callback Callback that performs the action triggering the event.
*/
Page waitForPage(WaitForPageOptions options, Runnable callback);
}
@@ -16,60 +16,56 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
* typical example of using Playwright to drive automation:
* <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");
* // other actions...
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface BrowserType {
class LaunchOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public LaunchOptions done() {
return LaunchOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
class ConnectOptions {
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found
* [here](http://peter.sh/experiments/chromium-command-line-switches/).
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public Double timeout;
public ConnectOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public ConnectOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class LaunchOptions {
/**
* 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 List<String> args;
/**
@@ -97,8 +93,8 @@ public interface BrowserType {
*/
public Path executablePath;
/**
* Firefox user preferences. Learn more about the Firefox user preferences at
* [{@code about:config}](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
* 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 Map<String, Object> firefoxUserPrefs;
/**
@@ -114,18 +110,22 @@ public interface BrowserType {
*/
public Boolean handleSIGTERM;
/**
* Whether to run browser in headless mode. More details for
* [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and
* [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to {@code true} unless the
* 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 Boolean headless;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. If an array is
* given, then filters out the given default arguments. Dangerous option; use with care. Defaults to {@code false}.
* 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 Boolean ignoreAllDefaultArgs;
/**
* 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 List<String> ignoreDefaultArgs;
public Boolean ignoreAllDefaultArgs;
/**
* Network proxy settings.
*/
@@ -140,198 +140,82 @@ public interface BrowserType {
*/
public Double timeout;
public LaunchOptions withArgs(List<String> args) {
public LaunchOptions setArgs(List<String> args) {
this.args = args;
return this;
}
public LaunchOptions withChromiumSandbox(boolean chromiumSandbox) {
public LaunchOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchOptions withDevtools(boolean devtools) {
public LaunchOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchOptions withDownloadsPath(Path downloadsPath) {
public LaunchOptions setDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchOptions withEnv(Map<String, String> env) {
public LaunchOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
public LaunchOptions withExecutablePath(Path executablePath) {
public LaunchOptions setExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
public LaunchOptions withFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
public LaunchOptions withHandleSIGHUP(boolean handleSIGHUP) {
public LaunchOptions setHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchOptions withHandleSIGINT(boolean handleSIGINT) {
public LaunchOptions setHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchOptions withHandleSIGTERM(boolean handleSIGTERM) {
public LaunchOptions setHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchOptions withHeadless(boolean headless) {
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
public LaunchOptions withIgnoreDefaultArgs(List<String> argumentNames) {
this.ignoreDefaultArgs = argumentNames;
public LaunchOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
public LaunchOptions withIgnoreAllDefaultArgs(boolean ignore) {
this.ignoreAllDefaultArgs = ignore;
public LaunchOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public LaunchOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
public LaunchOptions withSlowMo(double slowMo) {
public LaunchOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public LaunchOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public LaunchOptions withTimeout(double timeout) {
public LaunchOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class LaunchPersistentContextOptions {
public class Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
Proxy() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy withUsername(String username) {
this.username = username;
return this;
}
public Proxy withPassword(String password) {
this.password = password;
return this;
}
}
public class RecordHar {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public Boolean omitContent;
/**
* Path on the filesystem to write the HAR file to.
*/
public Path path;
RecordHar() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}
public class RecordVideo {
public class Size {
/**
* Video frame width.
*/
public int width;
/**
* Video frame height.
*/
public int height;
Size() {
}
public RecordVideo done() {
return RecordVideo.this;
}
public Size withWidth(int width) {
this.width = width;
return this;
}
public Size withHeight(int height) {
this.height = height;
return this;
}
}
/**
* Path to the directory to put videos into.
*/
public Path dir;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to {@code viewport}. If {@code viewport} is not
* configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary
* to fit the specified size.
*/
public Size size;
RecordVideo() {
}
public LaunchPersistentContextOptions done() {
return LaunchPersistentContextOptions.this;
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public Size setSize() {
this.size = new Size();
return this.size;
}
}
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found
* [here](http://peter.sh/experiments/chromium-command-line-switches/).
* 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 List<String> args;
/**
@@ -343,8 +227,8 @@ public interface BrowserType {
*/
public Boolean chromiumSandbox;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See
* [{@code method: Page.emulateMedia}] for more details. Defaults to '{@code light}'.
* 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 ColorScheme colorScheme;
/**
@@ -393,22 +277,26 @@ public interface BrowserType {
*/
public Boolean hasTouch;
/**
* Whether to run browser in headless mode. More details for
* [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and
* [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to {@code true} unless the
* 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 Boolean headless;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public HttpCredentials httpCredentials;
/**
* If {@code true}, then do not use any of the default arguments. If an array is given, then filter out the given default
* arguments. Dangerous option; use with care. Defaults to {@code false}.
* 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 Boolean ignoreAllDefaultArgs;
/**
* 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 List<String> ignoreDefaultArgs;
public Boolean ignoreAllDefaultArgs;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
@@ -432,8 +320,8 @@ public interface BrowserType {
*/
public Boolean offline;
/**
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
*/
public List<String> permissions;
/**
@@ -441,15 +329,23 @@ public interface BrowserType {
*/
public Proxy proxy;
/**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into {@code recordHar.path} file. If not
* specified, the HAR is not recorded. Make sure to await [{@code method: BrowserContext.close}] for the HAR to be saved.
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
*/
public RecordHar recordHar;
public Boolean recordHarOmitContent;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make
* sure to await [{@code method: BrowserContext.close}] for videos to be saved.
* Path on the filesystem to write the HAR file to.
*/
public RecordVideo recordVideo;
public Path recordHarPath;
/**
* Path to the directory to put videos into.
*/
public Path recordVideoDir;
/**
* 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 RecordVideoSize recordVideoSize;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@@ -461,9 +357,9 @@ public interface BrowserType {
*/
public Double timeout;
/**
* Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
* for a list of supported timezone IDs.
* 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 String timezoneId;
/**
@@ -473,149 +369,215 @@ public interface BrowserType {
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public Optional<ViewportSize> viewportSize;
public LaunchPersistentContextOptions withAcceptDownloads(boolean acceptDownloads) {
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public LaunchPersistentContextOptions withArgs(List<String> args) {
public LaunchPersistentContextOptions setArgs(List<String> args) {
this.args = args;
return this;
}
public LaunchPersistentContextOptions withBypassCSP(boolean bypassCSP) {
public LaunchPersistentContextOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public LaunchPersistentContextOptions withChromiumSandbox(boolean chromiumSandbox) {
public LaunchPersistentContextOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchPersistentContextOptions withColorScheme(ColorScheme colorScheme) {
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public LaunchPersistentContextOptions withDeviceScaleFactor(double deviceScaleFactor) {
public LaunchPersistentContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public LaunchPersistentContextOptions withDevtools(boolean devtools) {
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchPersistentContextOptions withDownloadsPath(Path downloadsPath) {
public LaunchPersistentContextOptions setDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchPersistentContextOptions withEnv(Map<String, String> env) {
public LaunchPersistentContextOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
public LaunchPersistentContextOptions withExecutablePath(Path executablePath) {
public LaunchPersistentContextOptions setExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
public LaunchPersistentContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
public LaunchPersistentContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public LaunchPersistentContextOptions withGeolocation(Geolocation geolocation) {
public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) {
return setGeolocation(new Geolocation(latitude, longitude));
}
public LaunchPersistentContextOptions setGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public LaunchPersistentContextOptions withHandleSIGHUP(boolean handleSIGHUP) {
public LaunchPersistentContextOptions setHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchPersistentContextOptions withHandleSIGINT(boolean handleSIGINT) {
public LaunchPersistentContextOptions setHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchPersistentContextOptions withHandleSIGTERM(boolean handleSIGTERM) {
public LaunchPersistentContextOptions setHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchPersistentContextOptions withHasTouch(boolean hasTouch) {
public LaunchPersistentContextOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public LaunchPersistentContextOptions withHeadless(boolean headless) {
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
public LaunchPersistentContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
public LaunchPersistentContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
public LaunchPersistentContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
public LaunchPersistentContextOptions withIgnoreDefaultArgs(List<String> argumentNames) {
this.ignoreDefaultArgs = argumentNames;
public LaunchPersistentContextOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
public LaunchPersistentContextOptions withIgnoreAllDefaultArgs(boolean ignore) {
this.ignoreAllDefaultArgs = ignore;
public LaunchPersistentContextOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
public LaunchPersistentContextOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
public LaunchPersistentContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public LaunchPersistentContextOptions withIsMobile(boolean isMobile) {
public LaunchPersistentContextOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public LaunchPersistentContextOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
public LaunchPersistentContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public LaunchPersistentContextOptions withLocale(String locale) {
public LaunchPersistentContextOptions setLocale(String locale) {
this.locale = locale;
return this;
}
public LaunchPersistentContextOptions withOffline(boolean offline) {
public LaunchPersistentContextOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
public LaunchPersistentContextOptions withPermissions(List<String> permissions) {
public LaunchPersistentContextOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
public LaunchPersistentContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
public LaunchPersistentContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
public RecordVideo setRecordVideo() {
this.recordVideo = new RecordVideo();
return this.recordVideo;
public LaunchPersistentContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
this.recordHarOmitContent = recordHarOmitContent;
return this;
}
public LaunchPersistentContextOptions withSlowMo(double slowMo) {
public LaunchPersistentContextOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
return this;
}
public LaunchPersistentContextOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
return this;
}
public LaunchPersistentContextOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
public LaunchPersistentContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
public LaunchPersistentContextOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public LaunchPersistentContextOptions withTimeout(double timeout) {
public LaunchPersistentContextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public LaunchPersistentContextOptions withTimezoneId(String timezoneId) {
public LaunchPersistentContextOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public LaunchPersistentContextOptions withUserAgent(String userAgent) {
public LaunchPersistentContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public LaunchPersistentContextOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
public LaunchPersistentContextOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
public LaunchPersistentContextOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
return this;
}
}
/**
* This methods attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
default Browser connect(String wsEndpoint) {
return connect(wsEndpoint, null);
}
/**
* This methods attaches Playwright to an existing browser instance.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
Browser connect(String wsEndpoint, ConnectOptions options);
/**
* A path where Playwright expects to find a bundled browser executable.
*/
String executablePath();
/**
* Returns the browser instance.
*
* <p> You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
* <pre>{@code
* // Or "firefox" or "webkit".
* Browser browser = chromium.launch(new BrowserType.LaunchOptions()
* .setIgnoreDefaultArgs(Arrays.asList("--mute-audio")));
* }</pre>
*
* <p> > **Chromium-only** Playwright can also be used to control the Google Chrome or Microsoft Edge browsers, but it works
* best with the version of Chromium it is bundled with. There is no guarantee it will work with any other version. Use
* {@code executablePath} option with extreme caution.
*
* <p> >
*
* <p> > If Google Chrome (rather than Chromium) is preferred, a <a
* href="https://www.google.com/chrome/browser/canary.html">Chrome Canary</a> or <a
* href="https://www.chromium.org/getting-involved/dev-channel">Dev Channel</a> build is suggested.
*
* <p> >
*
* <p> > Stock browsers like Google Chrome and Microsoft Edge are suitable for tests that require proprietary media codecs for
* video playback. See <a
* href="https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/">this article</a> for
* other differences between Chromium and Chrome. <a
* href="https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md">This article</a>
* describes some differences for Linux users.
*/
default Browser launch() {
return launch(null);
}
@@ -623,23 +585,43 @@ public interface BrowserType {
* Returns the browser instance.
*
* <p> You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
* <pre>{@code
* // Or "firefox" or "webkit".
* Browser browser = chromium.launch(new BrowserType.LaunchOptions()
* .setIgnoreDefaultArgs(Arrays.asList("--mute-audio")));
* }</pre>
*
* <p> **Chromium-only** Playwright can also be used to control the Chrome browser, but it works best with the version of
* Chromium it is bundled with. There is no guarantee it will work with any other version. Use {@code executablePath} option with
* extreme caution.
* >
* If Google Chrome (rather than Chromium) is preferred, a
* [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or
* [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested.
* >
* In [{@code method: BrowserType.launch}] above, any mention of Chromium also applies to Chrome.
* >
* See [{@code this article}](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for
* a description of the differences between Chromium and Chrome.
* [{@code This article}](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md)
* <p> > **Chromium-only** Playwright can also be used to control the Google Chrome or Microsoft Edge browsers, but it works
* best with the version of Chromium it is bundled with. There is no guarantee it will work with any other version. Use
* {@code executablePath} option with extreme caution.
*
* <p> >
*
* <p> > If Google Chrome (rather than Chromium) is preferred, a <a
* href="https://www.google.com/chrome/browser/canary.html">Chrome Canary</a> or <a
* href="https://www.chromium.org/getting-involved/dev-channel">Dev Channel</a> build is suggested.
*
* <p> >
*
* <p> > Stock browsers like Google Chrome and Microsoft Edge are suitable for tests that require proprietary media codecs for
* video playback. See <a
* href="https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/">this article</a> for
* other differences between Chromium and Chrome. <a
* href="https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md">This article</a>
* describes some differences for Linux users.
*/
Browser launch(LaunchOptions options);
/**
* Returns the persistent browser context instance.
*
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
return launchPersistentContext(userDataDir, null);
}
@@ -649,13 +631,14 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for
* [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) and
* [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile).
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
/**
* Returns browser name. For example: {@code 'chromium'}, {@code 'webkit'} or {@code 'firefox'}.
* Returns browser name. For example: {@code "chromium"}, {@code "webkit"} or {@code "firefox"}.
*/
String name();
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import java.io.IOException;
import java.nio.file.Path;
import static java.util.Arrays.asList;
/**
* Use this class to launch playwright cli.
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled();
ProcessBuilder pb = new ProcessBuilder(driver.toString());
pb.command().addAll(asList(args));
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
pb.environment().put("PW_CLI_TARGET_LANG", "java");
}
pb.inheritIO();
Process process = pb.start();
System.exit(process.waitFor());
}
}
@@ -1,3 +0,0 @@
package com.microsoft.playwright;
public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE }
@@ -19,40 +19,19 @@ package com.microsoft.playwright;
import java.util.*;
/**
* {@code ConsoleMessage} objects are dispatched by page via the [{@code event: Page.console}] event.
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsole Page.onConsole()} event.
*/
public interface ConsoleMessage {
class Location {
/**
* URL of the resource.
*/
private String url;
/**
* 0-based line number in the resource.
*/
private int lineNumber;
/**
* 0-based column number in the resource.
*/
private int columnNumber;
public String url() {
return this.url;
}
public int lineNumber() {
return this.lineNumber;
}
public int columnNumber() {
return this.columnNumber;
}
}
List<JSHandle> args();
Location location();
/**
* URL of the resource followed by 0-based line and column numbers in the resource formatted as {@code URL:line:column}.
*/
String location();
String text();
/**
* One of the following values: {@code 'log'}, {@code 'debug'}, {@code 'info'}, {@code 'error'}, {@code 'warning'}, {@code 'dir'}, {@code 'dirxml'}, {@code 'table'},
* {@code 'trace'}, {@code 'clear'}, {@code 'startGroup'}, {@code 'startGroupCollapsed'}, {@code 'endGroup'}, {@code 'assert'}, {@code 'profile'}, {@code 'profileEnd'},
* {@code 'count'}, {@code 'timeEnd'}.
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code "dir"}, {@code "dirxml"}, {@code "table"},
* {@code "trace"}, {@code "clear"}, {@code "startGroup"}, {@code "startGroupCollapsed"}, {@code "endGroup"}, {@code "assert"}, {@code "profile"}, {@code "profileEnd"},
* {@code "count"}, {@code "timeEnd"}.
*/
String type();
}
@@ -19,11 +19,39 @@ package com.microsoft.playwright;
import java.util.*;
/**
* {@code Dialog} objects are dispatched by page via the [{@code event: Page.dialog}] event.
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
*
* <p> An example of using {@code Dialog} class:
* <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.onDialog(dialog -> {
* System.out.println(dialog.message());
* dialog.dismiss();
* });
* page.evaluate("alert('1')");
* browser.close();
* }
* }
* }
* }</pre>
*
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link Page#onDialog Page.onDialog()} listener. When listener is
* present, it **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()} the dialog
* - otherwise the page will <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
* dialog, and actions like click will never finish.
*/
public interface Dialog {
enum Type { ALERT, BEFOREUNLOAD, CONFIRM, PROMPT }
/**
* Returns when the dialog has been accepted.
*/
default void accept() {
accept(null);
}
@@ -48,6 +76,6 @@ public interface Dialog {
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
*/
Type type();
String type();
}
@@ -21,16 +21,30 @@ import java.nio.file.Path;
import java.util.*;
/**
* {@code Download} objects are dispatched by page via the [{@code event: Page.download}] event.
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
* files are deleted when the browser closes.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.click("a"));
* // wait for download to complete
* Path path = download.path();
* }</pre>
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> {
* page.click("a");
* });
* // wait for download to complete
* Path path = download.path();
* }</pre>
*
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the
* downloaded content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not
* performed and user has no access to the downloaded files.
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the downloaded
* content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not performed and user
* has no access to the downloaded files.
*/
public interface Download {
/**
@@ -56,10 +70,11 @@ public interface Download {
*/
void saveAs(Path path);
/**
* Returns suggested filename for this download. It is typically computed by the browser from the
* [{@code Content-Disposition}](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) response header
* or the {@code download} attribute. See the spec on [whatwg](https://html.spec.whatwg.org/#downloading-resources). Different
* browsers can use different logic for computing it.
* Returns suggested filename for this download. It is typically computed by the browser from the <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a> response
* header or the {@code download} attribute. See the spec on <a
* href="https://html.spec.whatwg.org/#downloading-resources">whatwg</a>. Different browsers can use different logic for
* computing it.
*/
String suggestedFilename();
/**
File diff suppressed because it is too large Load Diff
@@ -16,25 +16,18 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* {@code FileChooser} objects are dispatched by the page in the [{@code event: Page.filechooser}] event.
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
public interface FileChooser {
class FilePayload {
public final String name;
public final String mimeType;
public final byte[] buffer;
public FilePayload(String name, String mimeType, byte[] buffer) {
this.name = name;
this.mimeType = mimeType;
this.buffer = buffer;
}
}
class SetFilesOptions {
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
@@ -44,15 +37,16 @@ public interface FileChooser {
public Boolean noWaitAfter;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the [{@code method: BrowserContext.setDefaultTimeout}] or [{@code method: Page.setDefaultTimeout}] methods.
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
public SetFilesOptions withNoWaitAfter(boolean noWaitAfter) {
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
public SetFilesOptions withTimeout(double timeout) {
public SetFilesOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
@@ -69,17 +63,53 @@ public interface FileChooser {
* Returns page this file chooser belongs to.
*/
Page page();
default void setFiles(Path file) { setFiles(file, null); }
default void setFiles(Path file, SetFilesOptions options) { setFiles(new Path[]{ file }, options); }
default void setFiles(Path[] files) { setFiles(files, null); }
void setFiles(Path[] files, SetFilesOptions options);
default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); }
default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); }
default void setFiles(FileChooser.FilePayload[] files) { setFiles(files, null); }
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(FileChooser.FilePayload[] files, SetFilesOptions options);
default void setFiles(Path files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(Path files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
default void setFiles(Path[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(Path[] files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
default void setFiles(FilePayload files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(FilePayload files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
default void setFiles(FilePayload[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(FilePayload[] files, SetFilesOptions options);
}
File diff suppressed because it is too large Load Diff
@@ -19,15 +19,19 @@ package com.microsoft.playwright;
import java.util.*;
/**
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the [{@code method: Page.evaluateHandle}]
* method.
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the {@link Page#evaluateHandle
* Page.evaluateHandle()} method.
* <pre>{@code
* JSHandle windowHandle = page.evaluateHandle("() => window");
* // ...
* }</pre>
*
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with
* [{@code method: JSHandle.dispose}]. JSHandles are auto-disposed when their origin frame gets navigated or the parent context
* gets destroyed.
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with {@link
* JSHandle#dispose JSHandle.dispose()}. JSHandles are auto-disposed when their origin frame gets navigated or the parent
* context gets destroyed.
*
* <p> JSHandle instances can be used as an argument in [{@code method: Page.$eval}], [{@code method: Page.evaluate}] and
* [{@code method: Page.evaluateHandle}] methods.
* <p> JSHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()}, {@link Page#evaluate
* Page.evaluate()} and {@link Page#evaluateHandle Page.evaluateHandle()} methods.
*/
public interface JSHandle {
/**
@@ -38,44 +42,95 @@ public interface JSHandle {
* The {@code jsHandle.dispose} method stops referencing the element handle.
*/
void dispose();
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
}
/**
* Returns the return value of {@code pageFunction}
* Returns the return value of {@code expression}.
*
* <p> This method passes this handle as the first argument to {@code pageFunction}.
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code pageFunction} returns a [Promise], then {@code handle.evaluate} would wait for the promise to resolve and return its
* value.
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> Examples:
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param pageFunction Function to be evaluated in browser context
* @param arg Optional argument to pass to {@code pageFunction}
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
Object evaluate(String pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
default Object evaluate(String expression) {
return evaluate(expression, null);
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* Returns the return value of {@code expression}.
*
* <p> This method passes this handle as the first argument to {@code pageFunction}.
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> Examples:
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* in-page object (JSHandle).
* {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a [Promise], then {@code jsHandle.evaluateHandle} would wait
* for the promise to resolve and return its value.
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See [{@code method: Page.evaluateHandle}] for more details.
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
*
* @param pageFunction Function to be evaluated
* @param arg Optional argument to pass to {@code pageFunction}
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
}
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String expression, Object arg);
/**
* The method returns a map with **own property names** as keys and JSHandle instances for the property values.
* <pre>{@code
* JSHandle handle = page.evaluateHandle("() => ({window, document}"););
* Map<String, JSHandle> properties = handle.getProperties();
* JSHandle windowHandle = properties.get("window");
* JSHandle documentHandle = properties.get("document");
* handle.dispose();
* }</pre>
*/
Map<String, JSHandle> getProperties();
/**
@@ -87,8 +142,8 @@ public interface JSHandle {
/**
* Returns a JSON representation of the object. If the object has a {@code toJSON} function, it **will not be called**.
*
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an
* error if the object has circular references.
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an error if the
* object has circular references.
*/
Object jsonValue();
}
@@ -16,27 +16,51 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is [{@code method: Keyboard.type}], which takes
* raw characters and generates proper keydown, keypress/input, and keyup events on your page.
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
* which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page.
*
* <p> For finer control, you can use [{@code method: Keyboard.down}], [{@code method: Keyboard.up}], and [{@code method: Keyboard.insertText}]
* to manually fire events as if they were generated from a real keyboard.
* <p> For finer control, you can use {@link Keyboard#down Keyboard.down()}, {@link Keyboard#up Keyboard.up()}, and {@link
* Keyboard#insertText Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
*
* <p> An example of holding down {@code Shift} in order to select and delete some text:
* <pre>{@code
* page.keyboard().type("Hello World!");
* page.keyboard().press("ArrowLeft");
* page.keyboard().down("Shift");
* for (int i = 0; i < " World".length(); i++)
* page.keyboard().press("ArrowLeft");
* page.keyboard().up("Shift");
* page.keyboard().press("Backspace");
* // Result text will end up saying "Hello!"
* }</pre>
*
* <p> An example of pressing uppercase {@code A}
* <pre>{@code
* page.keyboard().press("Shift+KeyA");
* // or
* page.keyboard().press("Shift+A");
* }</pre>
*
* <p> An example to trigger select-all with the keyboard
* <pre>{@code
* // on Windows and Linux
* page.keyboard().press("Control+A");
* // on macOS
* page.keyboard().press("Meta+A");
* }</pre>
*/
public interface Keyboard {
enum Modifier { ALT, CONTROL, META, SHIFT }
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public Double delay;
public PressOptions withDelay(double delay) {
public PressOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@@ -47,7 +71,7 @@ public interface Keyboard {
*/
public Double delay;
public TypeOptions withDelay(double delay) {
public TypeOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@@ -55,9 +79,10 @@ public interface Keyboard {
/**
* Dispatches a {@code keydown} event.
*
* <p> {@code key} can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). Examples of the keys are:
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
@@ -70,11 +95,11 @@ public interface Keyboard {
* texts.
*
* <p> If {@code key} is a modifier key, {@code Shift}, {@code Meta}, {@code Control}, or {@code Alt}, subsequent key presses will be sent with that modifier
* active. To release the modifier key, use [{@code method: Keyboard.up}].
* active. To release the modifier key, use {@link Keyboard#up Keyboard.up()}.
*
* <p> After the key is pressed once, subsequent calls to [{@code method: Keyboard.down}] will have
* [repeat](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat) set to true. To release the key, use
* [{@code method: Keyboard.up}].
* <p> After the key is pressed once, subsequent calls to {@link Keyboard#down Keyboard.down()} will have <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat">repeat</a> set to true. To release the key,
* use {@link Keyboard#up Keyboard.up()}.
*
* <p> <strong>NOTE:</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
@@ -83,19 +108,20 @@ public interface Keyboard {
void down(String key);
/**
* Dispatches only {@code input} event, does not emit the {@code keydown}, {@code keyup} or {@code keypress} events.
* <pre>{@code
* page.keyboard().insertText("嗨");
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper case.
*
* @param text Sets input to the specified text value.
*/
void insertText(String text);
default void press(String key) {
press(key, null);
}
/**
* {@code key} can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). Examples of the keys are:
* {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
@@ -109,25 +135,94 @@ public interface Keyboard {
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When speficied with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for [{@code method: Keyboard.down}] and [{@code method: Keyboard.up}].
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
void press(String key, PressOptions delay);
default void press(String key) {
press(key, null);
}
/**
* {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When speficied with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
void press(String key, PressOptions options);
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* @param text A text to type into a focused element.
*/
default void type(String text) {
type(text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use [{@code method: Keyboard.press}].
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* @param text A text to type into a focused element.
*/
void type(String text, TypeOptions delay);
void type(String text, TypeOptions options);
/**
* Dispatches a {@code keyup} event.
*
@@ -16,21 +16,30 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
*
* <p> Every {@code page} object has its own Mouse, accessible with [{@code property: Page.mouse}].
* <p> Every {@code page} object has its own Mouse, accessible with {@link Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
* page.mouse().move(0, 0);
* page.mouse().down();
* page.mouse().move(0, 100);
* page.mouse().move(100, 100);
* page.mouse().move(100, 0);
* page.mouse().move(0, 0);
* page.mouse().up();
* }</pre>
*/
public interface Mouse {
enum Button { LEFT, MIDDLE, RIGHT }
class ClickOptions {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See [UIEvent.detail].
*/
@@ -40,15 +49,15 @@ public interface Mouse {
*/
public Double delay;
public ClickOptions withButton(Button button) {
public ClickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public ClickOptions withClickCount(int clickCount) {
public ClickOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
public ClickOptions withDelay(double delay) {
public ClickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@@ -57,17 +66,17 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Double delay;
public DblclickOptions withButton(Button button) {
public DblclickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public DblclickOptions withDelay(double delay) {
public DblclickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@@ -76,17 +85,17 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
public DownOptions withButton(Button button) {
public DownOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public DownOptions withClickCount(int clickCount) {
public DownOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
@@ -97,7 +106,7 @@ public interface Mouse {
*/
public Integer steps;
public MoveOptions withSteps(int steps) {
public MoveOptions setSteps(int steps) {
this.steps = steps;
return this;
}
@@ -106,36 +115,46 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
public UpOptions withButton(Button button) {
public UpOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public UpOptions withClickCount(int clickCount) {
public UpOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
*/
default void click(double x, double y) {
click(x, y, null);
}
/**
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}].
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
*/
void click(double x, double y, ClickOptions options);
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
*/
default void dblclick(double x, double y) {
dblclick(x, y, null);
}
/**
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}], [{@code method: Mouse.down}] and
* [{@code method: Mouse.up}].
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
*/
void dblclick(double x, double y, DblclickOptions options);
/**
* Dispatches a {@code mousedown} event.
*/
default void down() {
down(null);
}
@@ -143,6 +162,9 @@ public interface Mouse {
* Dispatches a {@code mousedown} event.
*/
void down(DownOptions options);
/**
* Dispatches a {@code mousemove} event.
*/
default void move(double x, double y) {
move(x, y, null);
}
@@ -150,6 +172,9 @@ public interface Mouse {
* Dispatches a {@code mousemove} event.
*/
void move(double x, double y, MoveOptions options);
/**
* Dispatches a {@code mouseup} event.
*/
default void up() {
up(null);
}
File diff suppressed because it is too large Load Diff
@@ -22,34 +22,58 @@ import java.util.*;
/**
* Playwright module provides a method to launch a browser instance. The following is a typical example of using Playwright
* to drive automation:
* <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("http://example.com");
* // other actions...
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface Playwright {
public interface Playwright extends AutoCloseable {
/**
* This object can be used to launch or connect to Chromium, returning instances of {@code ChromiumBrowser}.
*/
BrowserType chromium();
/**
* Returns a dictionary of devices to be used with [{@code method: Browser.newContext}] or [{@code method: Browser.newPage}].
*/
Map<String, DeviceDescriptor> devices();
/**
* This object can be used to launch or connect to Firefox, returning instances of {@code FirefoxBrowser}.
*/
BrowserType firefox();
/**
* Selectors can be used to install custom selector engines. See
* [Working with selectors](./selectors.md#working-with-selectors) for more information.
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
*/
Selectors selectors();
/**
* This object can be used to launch or connect to WebKit, returning instances of {@code WebKitBrowser}.
*/
BrowserType webkit();
/**
* Terminates this instance of Playwright, will also close all created browsers if they are still running.
*/
void close();
/**
* Launches new Playwright driver process and connects to it. {@link Playwright#close Playwright.close()} should be called
* when the instance is no longer needed.
* <pre>{@code
* Playwright playwright = Playwright.create()) {
* Browser browser = playwright.webkit().launch();
* Page page = browser.newPage();
* page.navigate("https://www.w3.org/");
* playwright.close();
* }</pre>
*/
static Playwright create() {
return PlaywrightImpl.create();
}
void close() throws Exception;
}
@@ -16,117 +16,39 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Whenever the page sends a request for a network resource the following sequence of events are emitted by {@code Page}:
* - [{@code event: Page.request}] emitted when the request is issued by the page.
* - [{@code event: Page.response}] emitted when/if the response status and headers are received for the request.
* - [{@code event: Page.requestfinished}] emitted when the response body is downloaded and the request is complete.
* <ul>
* <li> {@link Page#onRequest Page.onRequest()} emitted when the request is issued by the page.</li>
* <li> {@link Page#onResponse Page.onResponse()} emitted when/if the response status and headers are received for the request.</li>
* <li> {@link Page#onRequestFinished Page.onRequestFinished()} emitted when the response body is downloaded and the request is
* complete.</li>
* </ul>
*
* <p> If request fails at some point, then instead of {@code 'requestfinished'} event (and possibly instead of 'response' event),
* the [{@code event: Page.requestfailed}] event is emitted.
* <p> If request fails at some point, then instead of {@code "requestfinished"} event (and possibly instead of 'response' event),
* the {@link Page#onRequestFailed Page.onRequestFailed()} event is emitted.
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will
* complete with {@code 'requestfinished'} event.
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@code "requestfinished"} event.
*
* <p> If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new
* request is issued to a redirected url.
*/
public interface Request {
class RequestFailure {
/**
* Human-readable error message, e.g. {@code 'net::ERR_FAILED'}.
*/
private String errorText;
public RequestFailure(String errorText) {
this.errorText = errorText;
}
public String errorText() {
return this.errorText;
}
}
class RequestTiming {
/**
* Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC
*/
private double startTime;
/**
* Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
private double domainLookupStart;
/**
* Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
private double domainLookupEnd;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double connectStart;
/**
* Time immediately before the browser starts the handshake process to secure the current connection. The value is given in
* milliseconds relative to {@code startTime}, -1 if not available.
*/
private double secureConnectionStart;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double connectEnd;
/**
* Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double requestStart;
/**
* Time immediately after the browser starts requesting the resource from the server, cache, or local resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double responseStart;
/**
* Time immediately after the browser receives the last byte of the resource or immediately before the transport connection
* is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double responseEnd;
public double startTime() {
return this.startTime;
}
public double domainLookupStart() {
return this.domainLookupStart;
}
public double domainLookupEnd() {
return this.domainLookupEnd;
}
public double connectStart() {
return this.connectStart;
}
public double secureConnectionStart() {
return this.secureConnectionStart;
}
public double connectEnd() {
return this.connectEnd;
}
public double requestStart() {
return this.requestStart;
}
public double responseStart() {
return this.responseStart;
}
public double responseEnd() {
return this.responseEnd;
}
}
/**
* The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event.
*
* <p> Example of logging of all the failed requests:
* <pre>{@code
* page.onRequestFailed(request -> {
* System.out.println(request.url() + " " + request.failure());
* });
* }</pre>
*/
RequestFailure failure();
String failure();
/**
* Returns the {@code Frame} that initiated this request.
*/
@@ -159,14 +81,25 @@ public interface Request {
* construct the whole redirect chain by repeatedly calling {@code redirectedFrom()}.
*
* <p> For example, if the website {@code http://example.com} redirects to {@code https://example.com}:
* <pre>{@code
* Response response = page.navigate("http://example.com");
* System.out.println(response.request().redirectedFrom().url()); // "http://example.com"
* }</pre>
*
* <p> If the website {@code https://google.com} has no redirects:
* <pre>{@code
* Response response = page.navigate("https://google.com");
* System.out.println(response.request().redirectedFrom()); // null
* }</pre>
*/
Request redirectedFrom();
/**
* New request issued by the browser if the server responded with redirect.
*
* <p> This method is the opposite of [{@code method: Request.redirectedFrom}]:
* <p> This method is the opposite of {@link Request#redirectedFrom Request.redirectedFrom()}:
* <pre>{@code
* System.out.println(request.redirectedFrom().redirectedTo() == request); // true
* }</pre>
*/
Request redirectedTo();
/**
@@ -181,10 +114,17 @@ public interface Request {
Response response();
/**
* 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
* [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming).
* {@code responseEnd} becomes available when request finishes. Find more information at <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">Resource Timing API</a>.
* <pre>{@code
* page.onRequestFinished(request -> {
* Timing timing = request.timing();
* System.out.println(timing.responseEnd - timing.startTime);
* });
* page.navigate("http://example.com");
* }</pre>
*/
RequestTiming timing();
Timing timing();
/**
* URL of the request.
*/
@@ -21,11 +21,11 @@ import java.nio.file.Path;
import java.util.*;
/**
* Whenever a network route is set up with [{@code method: Page.route}] or [{@code method: BrowserContext.route}], the {@code Route} object
* allows to handle the route.
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
*/
public interface Route {
class ContinueOptions {
class ResumeOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
@@ -37,38 +37,41 @@ public interface Route {
/**
* If set changes the post data of request
*/
public byte[] postData;
public Object postData;
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public String url;
public ContinueOptions withHeaders(Map<String, String> headers) {
public ResumeOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public ContinueOptions withMethod(String method) {
public ResumeOptions setMethod(String method) {
this.method = method;
return this;
}
public ContinueOptions withPostData(String postData) {
this.postData = postData.getBytes(StandardCharsets.UTF_8);
return this;
}
public ContinueOptions withPostData(byte[] postData) {
public ResumeOptions setPostData(String postData) {
this.postData = postData;
return this;
}
public ContinueOptions withUrl(String url) {
public ResumeOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
public ResumeOptions setUrl(String url) {
this.url = url;
return this;
}
}
class FulfillOptions {
/**
* Response body.
* Optional response body as text.
*/
public String body;
/**
* Optional response body as raw bytes.
*/
public byte[] bodyBytes;
/**
* If set, equals to setting {@code Content-Type} response header.
@@ -88,31 +91,34 @@ public interface Route {
*/
public Integer status;
public FulfillOptions withBody(byte[] body) {
this.bodyBytes = body;
return this;
}
public FulfillOptions withBody(String body) {
public FulfillOptions setBody(String body) {
this.body = body;
return this;
}
public FulfillOptions withContentType(String contentType) {
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
this.bodyBytes = bodyBytes;
return this;
}
public FulfillOptions setContentType(String contentType) {
this.contentType = contentType;
return this;
}
public FulfillOptions withHeaders(Map<String, String> headers) {
public FulfillOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public FulfillOptions withPath(Path path) {
public FulfillOptions setPath(Path path) {
this.path = path;
return this;
}
public FulfillOptions withStatus(int status) {
public FulfillOptions setStatus(int status) {
this.status = status;
return this;
}
}
/**
* Aborts the route's request.
*/
default void abort() {
abort(null);
}
@@ -120,36 +126,94 @@ public interface Route {
* Aborts the route's request.
*
* @param errorCode Optional error code. Defaults to {@code failed}, could be one of the following:
* - {@code 'aborted'} - An operation was aborted (due to user action)
* - {@code 'accessdenied'} - Permission to access a resource, other than the network, was denied
* - {@code 'addressunreachable'} - The IP address is unreachable. This usually means that there is no route to the specified
* host or network.
* - {@code 'blockedbyclient'} - The client chose to block the request.
* - {@code 'blockedbyresponse'} - The request failed because the response was delivered along with requirements which are not
* met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
* - {@code 'connectionaborted'} - A connection timed out as a result of not receiving an ACK for data sent.
* - {@code 'connectionclosed'} - A connection was closed (corresponding to a TCP FIN).
* - {@code 'connectionfailed'} - A connection attempt failed.
* - {@code 'connectionrefused'} - A connection attempt was refused.
* - {@code 'connectionreset'} - A connection was reset (corresponding to a TCP RST).
* - {@code 'internetdisconnected'} - The Internet connection has been lost.
* - {@code 'namenotresolved'} - The host name could not be resolved.
* - {@code 'timedout'} - An operation timed out.
* - {@code 'failed'} - A generic failure occurred.
* <ul>
* <li> {@code "aborted"} - An operation was aborted (due to user action)</li>
* <li> {@code "accessdenied"} - Permission to access a resource, other than the network, was denied</li>
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified host
* or network.</li>
* <li> {@code "blockedbyclient"} - The client chose to block the request.</li>
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are not met
* ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
* <li> {@code "connectionaborted"} - A connection timed out as a result of not receiving an ACK for data sent.</li>
* <li> {@code "connectionclosed"} - A connection was closed (corresponding to a TCP FIN).</li>
* <li> {@code "connectionfailed"} - A connection attempt failed.</li>
* <li> {@code "connectionrefused"} - A connection attempt was refused.</li>
* <li> {@code "connectionreset"} - A connection was reset (corresponding to a TCP RST).</li>
* <li> {@code "internetdisconnected"} - The Internet connection has been lost.</li>
* <li> {@code "namenotresolved"} - The host name could not be resolved.</li>
* <li> {@code "timedout"} - An operation timed out.</li>
* <li> {@code "failed"} - A generic failure occurred.</li>
* </ul>
*/
void abort(String errorCode);
default void continue_() {
continue_(null);
/**
* Continues route's request with optional overrides.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
default void resume() {
resume(null);
}
/**
* Continues route's request with optional overrides.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
void resume(ResumeOptions options);
/**
* Fulfills route's request with given response.
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <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")));
* }</pre>
*/
void continue_(ContinueOptions options);
default void fulfill() {
fulfill(null);
}
/**
* Fulfills route's request with given response.
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <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")));
* }</pre>
*/
void fulfill(FulfillOptions options);
/**
@@ -20,8 +20,8 @@ import java.nio.file.Path;
import java.util.*;
/**
* Selectors can be used to install custom selector engines. See
* [Working with selectors](./selectors.md#working-with-selectors) for more information.
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
*/
public interface Selectors {
class RegisterOptions {
@@ -32,22 +32,146 @@ public interface Selectors {
*/
public Boolean contentScript;
public RegisterOptions withContentScript(boolean contentScript) {
public RegisterOptions setContentScript(boolean contentScript) {
this.contentScript = contentScript;
return this;
}
}
default void register(String name, String script) { register(name, script, null); }
void register(String name, String script, RegisterOptions options);
default void register(String name, Path path) { register(name, path, null); }
/**
* An example of registering selector engine that queries elements based on a tag name:
*
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
void register(String name, Path path, RegisterOptions options);
default void register(String name, String script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
void register(String name, String script, RegisterOptions options);
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
default void register(String name, Path script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* ElementHandle button = page.querySelector("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
void register(String name, Path script, RegisterOptions options);
}
@@ -16,14 +16,17 @@
package com.microsoft.playwright;
import java.util.*;
/**
* - extends: [Error]
*
* <p> TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [{@code method: Page.waitForSelector}]
* or [{@code method: BrowserType.launch}].
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. {@link Page#waitForSelector
* Page.waitForSelector()} or {@link BrowserType#launch BrowserType.launch()}.
*/
public interface TimeoutError {
public class TimeoutError extends PlaywrightException {
public TimeoutError(String message) {
super(message);
}
public TimeoutError(String message, Throwable exception) {
super(message, exception);
}
}
@@ -21,6 +21,9 @@ import java.util.*;
/**
* When browser context is created with the {@code videosPath} option, each page has a video object associated with it.
* <pre>{@code
* System.out.println(page.video().path());
* }</pre>
*/
public interface Video {
/**
@@ -24,55 +24,83 @@ import java.util.function.Predicate;
* The {@code WebSocket} class represents websocket connections in the page.
*/
public interface WebSocket {
interface FrameData {
byte[] body();
String text();
}
/**
* Fired when the websocket closes.
*/
void onClose(Consumer<WebSocket> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<WebSocket> handler);
void onFrameReceived(Consumer<FrameData> handler);
void offFrameReceived(Consumer<FrameData> handler);
/**
* Fired when the websocket recieves a frame.
*/
void onFrameReceived(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameReceived onFrameReceived(handler)}.
*/
void offFrameReceived(Consumer<WebSocketFrame> handler);
void onFrameSent(Consumer<FrameData> handler);
void offFrameSent(Consumer<FrameData> handler);
/**
* Fired when the websocket sends a frame.
*/
void onFrameSent(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameSent onFrameSent(handler)}.
*/
void offFrameSent(Consumer<WebSocketFrame> handler);
/**
* Fired when the websocket has an error.
*/
void onSocketError(Consumer<String> handler);
/**
* Removes handler that was previously added with {@link #onSocketError onSocketError(handler)}.
*/
void offSocketError(Consumer<String> handler);
class WaitForFrameReceivedOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* 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 Double timeout;
public WaitForFrameReceivedOptions withTimeout(double timeout) {
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
public WaitForFrameReceivedOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options);
default FrameData waitForFrameReceived(Runnable code) { return waitForFrameReceived(code, null); }
class WaitForFrameSentOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* 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 Double timeout;
public WaitForFrameSentOptions withTimeout(double timeout) {
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
public WaitForFrameSentOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options);
default FrameData waitForFrameSent(Runnable code) { return waitForFrameSent(code, null); }
class WaitForSocketErrorOptions {
public Double timeout;
public WaitForSocketErrorOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
String waitForSocketError(Runnable code, WaitForSocketErrorOptions options);
default String waitForSocketError(Runnable code) { return waitForSocketError(code, null); }
/**
* Indicates that the web socket has been closed.
*/
@@ -81,5 +109,41 @@ public interface WebSocket {
* Contains the URL of the WebSocket.
*/
String url();
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
*/
default WebSocketFrame waitForFrameReceived(Runnable callback) {
return waitForFrameReceived(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
*/
WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable callback);
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
*/
default WebSocketFrame waitForFrameSent(Runnable callback) {
return waitForFrameSent(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
*/
WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable callback);
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.*;
/**
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
* either {@link WebSocketFrame#text WebSocketFrame.text()} or {@link WebSocketFrame#binary WebSocketFrame.binary()} method
* depending on the its type.
*/
public interface WebSocketFrame {
/**
* Returns binary payload.
*/
byte[] binary();
/**
* Returns text payload.
*/
String text();
}
@@ -20,59 +20,121 @@ import java.util.*;
import java.util.function.Consumer;
/**
* The Worker class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). {@code worker}
* event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object when the
* worker is gone.
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object
* when the worker is gone.
* <pre>{@code
* page.onWorker(worker -> {
* System.out.println("Worker created: " + worker.url());
* worker.onClose(worker1 -> System.out.println("Worker destroyed: " + worker1.url()));
* });
* System.out.println("Current workers:");
* for (Worker worker : page.workers())
* System.out.println(" " + worker.url());
* }</pre>
*/
public interface Worker {
/**
* Emitted when this dedicated <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a> is
* terminated.
*/
void onClose(Consumer<Worker> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<Worker> handler);
class WaitForCloseOptions {
/**
* 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 Double timeout;
public WaitForCloseOptions withTimeout(double timeout) {
public WaitForCloseOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Worker waitForClose(Runnable code, WaitForCloseOptions options);
default Worker waitForClose(Runnable code) { return waitForClose(code, null); }
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
/**
* Returns the return value of {@code expression}.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
}
/**
* Returns the return value of {@code pageFunction}
* Returns the return value of {@code expression}.
*
* <p> If the function passed to the {@code worker.evaluate} returns a [Promise], then {@code worker.evaluate} would wait for the promise
* to resolve and return its value.
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
*
* <p> If the function passed to the {@code worker.evaluate} returns a non-[Serializable] value, then {@code worker.evaluate} returns
* {@code undefined}. DevTools Protocol also supports transferring some additional values that are not serializable by {@code JSON}:
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}, and bigint literals.
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @param pageFunction Function to be evaluated in the worker context
* @param arg Optional argument to pass to {@code pageFunction}
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
*/
Object evaluate(String pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
* in-page object (JSHandle).
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function passed to the {@code worker.evaluateHandle} returns a [Promise], then {@code worker.evaluateHandle} would wait for
* the promise to resolve and return its value.
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
*
* @param pageFunction Function to be evaluated in the page context
* @param arg Optional argument to pass to {@code pageFunction}
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
JSHandle evaluateHandle(String expression, Object arg);
String url();
/**
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
*/
default Worker waitForClose(Runnable callback) {
return waitForClose(null, callback);
}
/**
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
*/
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Accessibility;
import com.microsoft.playwright.AccessibilityNode;
import static com.microsoft.playwright.impl.Serialization.gson;
class AccessibilityImpl implements Accessibility {
private final PageImpl page;
AccessibilityImpl(PageImpl page) {
this.page = page;
}
@Override
public AccessibilityNode snapshot(SnapshotOptions options) {
return page.withLogging("Accessibility.snapshot", () -> snapshotImpl(options));
}
private AccessibilityNode snapshotImpl(SnapshotOptions options) {
if (options == null) {
options = new SnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = page.sendMessage("accessibilitySnapshot", params).getAsJsonObject();
if (!json.has("rootAXNode")) {
return null;
}
return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode"));
}
}
@@ -1,258 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.AccessibilityNode;
import java.util.ArrayList;
import java.util.List;
class AccessibilityNodeImpl implements AccessibilityNode {
private final JsonObject json;
AccessibilityNodeImpl(JsonObject json) {
this.json = json;
}
@Override
public String role() {
return json.get("role").getAsString();
}
@Override
public String name() {
return json.get("name").getAsString();
}
@Override
public String valueString() {
if (!json.has("valueString")) {
return null;
}
return json.get("valueString").getAsString();
}
@Override
public Double valueNumber() {
if (!json.has("valueNumber")) {
return null;
}
return json.get("valueNumber").getAsDouble();
}
@Override
public String description() {
if (!json.has("description")) {
return null;
}
return json.get("description").getAsString();
}
@Override
public String keyshortcuts() {
if (!json.has("keyshortcuts")) {
return null;
}
return json.get("keyshortcuts").getAsString();
}
@Override
public String roledescription() {
if (!json.has("roledescription")) {
return null;
}
return json.get("roledescription").getAsString();
}
@Override
public String valuetext() {
if (!json.has("valuetext")) {
return null;
}
return json.get("valuetext").getAsString();
}
@Override
public Boolean disabled() {
if (!json.has("disabled")) {
return null;
}
return json.get("disabled").getAsBoolean();
}
@Override
public Boolean expanded() {
if (!json.has("expanded")) {
return null;
}
return json.get("expanded").getAsBoolean();
}
@Override
public Boolean focused() {
if (!json.has("focused")) {
return null;
}
return json.get("focused").getAsBoolean();
}
@Override
public Boolean modal() {
if (!json.has("modal")) {
return null;
}
return json.get("modal").getAsBoolean();
}
@Override
public Boolean multiline() {
if (!json.has("multiline")) {
return null;
}
return json.get("multiline").getAsBoolean();
}
@Override
public Boolean multiselectable() {
if (!json.has("multiselectable")) {
return null;
}
return json.get("multiselectable").getAsBoolean();
}
@Override
public Boolean readonly() {
if (!json.has("readonly")) {
return null;
}
return json.get("readonly").getAsBoolean();
}
@Override
public Boolean required() {
if (!json.has("required")) {
return null;
}
return json.get("required").getAsBoolean();
}
@Override
public Boolean selected() {
if (!json.has("selected")) {
return null;
}
return json.get("selected").getAsBoolean();
}
@Override
public CheckedState checked() {
if (!json.has("checked")) {
return null;
}
String value = json.get("checked").getAsString();
switch (value) {
case "checked": return CheckedState.CHECKED;
case "unchecked": return CheckedState.UNCHECKED;
case "mixed": return CheckedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public PressedState pressed() {
if (!json.has("pressed")) {
return null;
}
String value = json.get("pressed").getAsString();
switch (value) {
case "pressed": return PressedState.PRESSED;
case "released": return PressedState.RELEASED;
case "mixed": return PressedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public Integer level() {
if (!json.has("level")) {
return null;
}
return json.get("level").getAsInt();
}
@Override
public Double valuemin() {
if (!json.has("valuemin")) {
return null;
}
return json.get("valuemin").getAsDouble();
}
@Override
public Double valuemax() {
if (!json.has("valuemax")) {
return null;
}
return json.get("valuemax").getAsDouble();
}
@Override
public String autocomplete() {
if (!json.has("autocomplete")) {
return null;
}
return json.get("autocomplete").getAsString();
}
@Override
public String haspopup() {
if (!json.has("haspopup")) {
return null;
}
return json.get("haspopup").getAsString();
}
@Override
public String invalid() {
if (!json.has("invalid")) {
return null;
}
return json.get("invalid").getAsString();
}
@Override
public String orientation() {
if (!json.has("orientation")) {
return null;
}
return json.get("orientation").getAsString();
}
@Override
public List<AccessibilityNode> children() {
if (!json.has("children")) {
return null;
}
List<AccessibilityNode> result = new ArrayList<>();
for (JsonElement e : json.getAsJsonArray("children")) {
result.add(new AccessibilityNodeImpl(e.getAsJsonObject()));
}
return result;
}
}
@@ -22,6 +22,7 @@ import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.BindingCallback;
import java.util.ArrayList;
import java.util.List;
@@ -29,7 +30,7 @@ import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
class BindingCall extends ChannelOwner {
private static class SourceImpl implements Page.Binding.Source {
private static class SourceImpl implements BindingCallback.Source {
private final Frame frame;
public SourceImpl(Frame frame) {
@@ -60,10 +61,10 @@ class BindingCall extends ChannelOwner {
return initializer.get("name").getAsString();
}
void call(Page.Binding binding) {
void call(BindingCallback binding) {
try {
Frame frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
Page.Binding.Source source = new SourceImpl(frame);
BindingCallback.Source source = new SourceImpl(frame);
List<Object> args = new ArrayList<>();
if (initializer.has("handle")) {
JSHandle handle = connection.getExistingObject(initializer.getAsJsonObject("handle").get("guid").getAsString());
@@ -20,10 +20,10 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
@@ -31,15 +31,18 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
final Map<String, Page.Binding> bindings = new HashMap<>();
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
final TimeoutSettings timeoutSettings = new TimeoutSettings();
@@ -88,7 +91,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public Page waitForPage(Runnable code, WaitForPageOptions options) {
public Page waitForPage(WaitForPageOptions options, Runnable code) {
if (options == null) {
options = new WaitForPageOptions();
}
@@ -99,6 +102,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
}
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? emptyList() : asList(url));
}
private void closeImpl() {
if (isClosedOrClosing) {
return;
@@ -114,7 +123,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void addCookies(List<AddCookie> cookies) {
public void addCookies(List<Cookie> cookies) {
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
@@ -123,15 +132,24 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void addInitScript(String script, Object arg) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script, arg));
public void addInitScript(String script) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script));
}
private void addInitScriptImpl(String script, Object arg) {
// TODO: serialize arg
@Override
public void addInitScript(Path path) {
withLogging("BrowserContext.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
});
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
if (isFunctionBody(script)) {
script = "(" + script + ")()";
}
params.addProperty("source", script);
sendMessage("addInitScript", params);
}
@@ -159,20 +177,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = Collections.emptyList();
urls = emptyList();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return Arrays.asList(cookies);
return asList(cookies);
}
@Override
public void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@@ -192,9 +210,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void exposeFunction(String name, Page.Function playwrightFunction) {
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
withLogging("BrowserContext.exposeFunction",
() -> exposeBindingImpl(name, (Page.Binding.Source source, Object... args) -> playwrightFunction.call(args), null));
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
@@ -207,7 +225,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new GrantPermissionsOptions();
}
if (permissions == null) {
permissions = Collections.emptyList();
permissions = emptyList();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
@@ -315,19 +333,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public StorageState storageState(StorageStateOptions options) {
public String storageState(StorageStateOptions options) {
return withLogging("BrowserContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
StorageState storageState = gson().fromJson(json, StorageState.class);
String storageState = json.toString();
if (options != null && options.path != null) {
try {
Files.createDirectories(options.path.getParent());
try (FileWriter writer = new FileWriter(options.path.toFile())) {
writer.write(json.toString());
}
} catch (IOException e) {
throw new PlaywrightException("Failed to write storage state to file", e);
}
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
});
@@ -370,13 +381,17 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
void pause() {
sendMessage("pause");
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
route.continue_();
route.resume();
}
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -384,16 +399,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
pages.add(page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
Page.Binding binding = bindings.get(bindingCall.name());
BindingCallback binding = bindings.get(bindingCall.name());
if (binding != null) {
bindingCall.call(binding);
}
} else if ("close".equals(event)) {
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
}
listeners.notify(EventType.CLOSE, this);
didClose();
}
}
void didClose() {
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
}
@@ -16,13 +16,17 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.PlaywrightException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -34,8 +38,9 @@ import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContext> contexts = new HashSet<>();
final Set<BrowserContextImpl> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
public boolean isRemote;
private boolean isConnected = true;
enum EventType {
@@ -60,7 +65,17 @@ class BrowserImpl extends ChannelOwner implements Browser {
public void close() {
withLogging("Browser.close", () -> closeImpl());
}
private void closeImpl() {
if (isRemote) {
try {
connection.close();
} catch (IOException e) {
throw new PlaywrightException("Failed to close browser connection", e);
}
notifyRemoteClosed();
return;
}
try {
sendMessage("close");
} catch (PlaywrightException e) {
@@ -70,6 +85,17 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
}
void notifyRemoteClosed() {
// Emulate all pages, contexts and the browser closing upon disconnect.
for (BrowserContextImpl context : new ArrayList<>(contexts)) {
for (PageImpl page : new ArrayList<>(context.pages)) {
page.didClose();
}
context.didClose();
}
didClose();
}
@Override
public List<BrowserContext> contexts() {
return new ArrayList<>(contexts);
@@ -90,18 +116,62 @@ class BrowserImpl extends ChannelOwner implements Browser {
options = new NewContextOptions();
}
if (options.storageStatePath != null) {
try (FileReader reader = new FileReader(options.storageStatePath.toFile())) {
options.storageState = gson().fromJson(reader, BrowserContext.StorageState.class);
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
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.recordVideo != null) {
context.videosDir = options.recordVideo.dir;
if (options.recordVideoDir != null) {
context.videosDir = options.recordVideoDir;
}
contexts.add(context);
return context;
@@ -136,8 +206,12 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
didClose();
}
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
}
}
@@ -18,13 +18,18 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.toProtocol;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -45,6 +50,47 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
}
@Override
public Browser connect(String wsEndpoint, ConnectOptions options) {
return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
}
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
try {
Duration timeout = Duration.ofDays(1);
if (options != null && options.timeout != null) {
timeout = Duration.ofMillis(Math.round(options.timeout));
}
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), timeout);
Connection connection = new Connection(transport);
RemoteBrowser remoteBrowser = (RemoteBrowser) connection.waitForObjectWithKnownName("remoteBrowser");
PlaywrightImpl playwright = this.connection.getExistingObject("Playwright");
SelectorsImpl selectors = remoteBrowser.selectors();
playwright.sharedSelectors.addChannel(selectors);
BrowserImpl browser = remoteBrowser.browser();
browser.isRemote = true;
Consumer<WebSocketTransport> connectionCloseListener = new Consumer<WebSocketTransport>() {
@Override
public void accept(WebSocketTransport t) {
browser.notifyRemoteClosed();
}
};
transport.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.sharedSelectors.removeChannel(selectors);
transport.offClose(connectionCloseListener);
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
});
return browser;
} catch (URISyntaxException e) {
throw new PlaywrightException("Failed to connect", e);
}
}
public String executablePath() {
return initializer.get("executablePath").getAsString();
}
@@ -61,10 +107,45 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("userDataDir", userDataDir.toString());
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
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.recordVideo != null) {
context.videosDir = options.recordVideo.dir;
if (options.recordVideoDir != null) {
context.videosDir = options.recordVideoDir;
}
return context;
}
@@ -16,13 +16,25 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
import java.io.*;
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;
import java.time.Duration;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -53,6 +65,7 @@ public class Connection {
private final Map<String, ChannelOwner> objects = new HashMap<>();
private final Root root;
private int lastId = 0;
private final Path srcDir;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
class Root extends ChannelOwner {
@@ -61,9 +74,18 @@ public class Connection {
}
}
public Connection(InputStream in, OutputStream out) {
transport = new Transport(in, out);
Connection(Transport transport) {
this.transport = transport;
root = new Root(this);
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
if (srcRoot == null) {
srcDir = null;
} else {
srcDir = Paths.get(srcRoot);
if (!Files.exists(srcDir)) {
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
}
}
}
void close() throws IOException {
@@ -78,6 +100,45 @@ public class Connection {
return internalSendMessage(guid, method, params);
}
private String sourceFile(StackTraceElement frame) {
String pkg = frame.getClassName();
int lastDot = pkg.lastIndexOf('.');
if (lastDot == -1) {
pkg = "";
} else {
pkg = frame.getClassName().substring(0, lastDot + 1);
}
pkg = pkg.replace('.', File.separatorChar);
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
}
private JsonArray currentStackTrace() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int index = 0;
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
index++;
};
// Find Playwright API call
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
// hack for tests
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
break;
}
index++;
}
JsonArray jsonStack = new JsonArray();
for (; index < stack.length; index++) {
StackTraceElement frame = stack[index];
JsonObject jsonFrame = new JsonObject();
jsonFrame.addProperty("file", sourceFile(frame));
jsonFrame.addProperty("line", frame.getLineNumber());
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
jsonStack.add(jsonFrame);
}
return jsonStack;
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
int id = ++lastId;
WaitableResult<JsonElement> result = new WaitableResult<>();
@@ -87,6 +148,11 @@ public class Connection {
message.addProperty("guid", guid);
message.addProperty("method", method);
message.add("params", params);
if (srcDir != null) {
JsonObject metadata = new JsonObject();
metadata.add("stack", currentStackTrace());
message.add("metadata", metadata);
}
transport.send(gson().toJson(message));
return result;
}
@@ -135,10 +201,12 @@ public class Connection {
if (message.error == null) {
callback.complete(message.result);
} else {
if (message.error.error != null) {
callback.completeExceptionally(new ServerException(message.error.error));
} else {
if (message.error.error == null) {
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
} else if ("TimeoutError".equals(message.error.error.name)) {
callback.completeExceptionally(new TimeoutError(message.error.error.toString()));
} else {
callback.completeExceptionally(new DriverException(message.error.error));
}
}
return;
@@ -229,6 +297,9 @@ public class Connection {
case "Request":
result = new RequestImpl(parent, type, guid, initializer);
break;
case "RemoteBrowser":
result = new RemoteBrowser(parent, type, guid, initializer);
break;
case "Response":
result = new ResponseImpl(parent, type, guid, initializer);
break;
@@ -48,7 +48,11 @@ public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
return result;
}
public Location location() {
return gson().fromJson(initializer.get("location"), Location.class);
@Override
public String location() {
JsonObject location = initializer.getAsJsonObject("location");
return location.get("url").getAsString() + ":" +
location.get("lineNumber").getAsNumber() + ":" +
location.get("columnNumber").getAsNumber();
}
}
@@ -1,80 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.DeviceDescriptor;
import com.microsoft.playwright.PlaywrightException;
class DeviceDescriptorImpl implements DeviceDescriptor {
PlaywrightImpl playwright;
private static class ViewportImpl implements Viewport{
private int width;
private int height;
@Override
public int width() {
return width;
}
@Override
public int height() {
return height;
}
}
private ViewportImpl viewport;
private String userAgent;
private double deviceScaleFactor;
private boolean isMobile;
private boolean hasTouch;
private String defaultBrowserType;
@Override
public Viewport viewport() {
return viewport;
}
@Override
public String userAgent() {
return userAgent;
}
@Override
public double deviceScaleFactor() {
return deviceScaleFactor;
}
@Override
public boolean isMobile() {
return isMobile;
}
@Override
public boolean hasTouch() {
return hasTouch;
}
@Override
public BrowserType defaultBrowserType() {
switch (defaultBrowserType) {
case "chromium": return playwright.chromium();
case "firefox": return playwright.firefox();
case "webkit": return playwright.webkit();
default: throw new PlaywrightException("Unknown type: " + defaultBrowserType);
}
}
}
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Dialog;
import com.microsoft.playwright.PlaywrightException;
class DialogImpl extends ChannelOwner implements Dialog {
DialogImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -52,13 +51,7 @@ class DialogImpl extends ChannelOwner implements Dialog {
}
@Override
public Type type() {
switch (initializer.get("type").getAsString()) {
case "alert": return Type.ALERT;
case "beforeunload": return Type.BEFOREUNLOAD;
case "confirm": return Type.CONFIRM;
case "prompt": return Type.PROMPT;
default: throw new PlaywrightException("Unexpected dialog type: " + initializer.get("type").getAsString());
}
public String type() {
return initializer.get("type").getAsString();
}
}
@@ -16,16 +16,27 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Download;
import com.microsoft.playwright.PlaywrightException;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.writeToFile;
public class DownloadImpl extends ChannelOwner implements Download {
private final BrowserImpl browser;
public DownloadImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
browser = ((BrowserContextImpl) parent).browser();
}
@Override
@@ -71,6 +82,9 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public Path path() {
return withLogging("Download.path", () -> {
if (browser != null && browser.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
});
@@ -79,6 +93,13 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public void saveAs(Path path) {
withLogging("Download.saveAs", () -> {
if (browser != null && browser.isRemote) {
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
writeToFile(stream.stream(), path);
return;
}
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
@@ -1,12 +1,12 @@
/*
* 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.
@@ -14,17 +14,15 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.impl;
public interface DeviceDescriptor {
interface Viewport {
int width();
int height();
import com.microsoft.playwright.PlaywrightException;
import java.io.PrintStream;
import java.io.PrintWriter;
class DriverException extends PlaywrightException {
DriverException(SerializedError.Error error) {
super(error.toString());
}
Viewport viewport();
String userAgent();
double deviceScaleFactor();
boolean isMobile();
boolean hasTouch();
BrowserType defaultBrowserType();
}
@@ -22,14 +22,20 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.ElementState;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -79,7 +85,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -93,7 +98,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -135,16 +139,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("click", params);
}
@@ -171,16 +165,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("dblclick", params);
}
@@ -227,10 +211,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
public void hover(HoverOptions options) {
withLogging("ElementHandle.hover", () -> {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("hover", params);
});
}
@@ -323,10 +303,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
sendMessage("press", params);
}
private static String toProtocol(ScreenshotOptions.Type type) {
return type.toString().toLowerCase();
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
@@ -337,21 +313,19 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new ScreenshotOptions();
}
if (options.type == null) {
options.type = ScreenshotOptions.Type.PNG;
options.type = PNG;
if (options.path != null) {
String fileName = options.path.getFileName().toString();
int extStart = fileName.lastIndexOf('.');
if (extStart != -1) {
String extension = fileName.substring(extStart).toLowerCase();
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
options.type = ScreenshotOptions.Type.JPEG;
options.type = JPEG;
}
}
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("type");
params.addProperty("type", toProtocol(options.type));
params.remove("path");
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
@@ -367,6 +341,33 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
}
@Override
public List<String> selectOption(String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
return selectOption(values, options);
}
@Override
public List<String> selectOption(ElementHandle value, SelectOptionOptions options) {
ElementHandle[] values = value == null ? null : new ElementHandle[]{ value };
return selectOption(values, options);
}
@Override
public List<String> selectOption(String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(new SelectOption[0], options);
}
return selectOption(Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
}
@Override
public List<String> selectOption(SelectOption value, SelectOptionOptions options) {
SelectOption[] values = value == null ? null : new SelectOption[]{ value };
return selectOption(values, options);
}
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
@@ -400,7 +401,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
private List<String> selectOption(JsonObject params) {
return withLogging("ElementHandle.selectOption", () -> {
return withLogging("SelectOption", () -> {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
});
@@ -411,6 +412,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
}
@Override
public void setInputFiles(Path files, SetInputFilesOptions options) {
setInputFiles(new Path[]{files}, options);
}
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
@@ -425,11 +431,16 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
setInputFiles(new FilePayload[]{files}, options);
}
@Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -448,10 +459,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.modifiers != null) {
params.remove("modifiers");
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("tap", params);
}
@@ -503,15 +510,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (options == null) {
options = new WaitForElementStateOptions();
}
if (state == null) {
throw new IllegalArgumentException("State cannot be null");
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
sendMessage("waitForElementState", params);
}
private static String toProtocol(ElementState state) {
if (state == null) {
throw new IllegalArgumentException("State cannot by null");
}
return state.toString().toLowerCase();
}
@@ -525,8 +532,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new WaitForSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("state");
params.addProperty("state", toProtocol(options.state));
params.addProperty("selector", selector);
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
@@ -535,11 +540,4 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
return connection.getExistingObject(element.get("guid").getAsString());
}
private static String toProtocol(WaitForSelectorOptions.State state) {
if (state == null) {
state = WaitForSelectorOptions.State.VISIBLE;
}
return state.toString().toLowerCase();
}
}
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.FilePayload;
import java.nio.file.Path;
@@ -50,11 +51,21 @@ class FileChooserImpl implements FileChooser {
return page;
}
@Override
public void setFiles(Path files, SetFilesOptions options) {
setFiles(new Path[]{files}, options);
}
@Override
public void setFiles(Path[] files, SetFilesOptions options) {
setFiles(Utils.toFilePayloads(files), options);
}
@Override
public void setFiles(FilePayload files, SetFilesOptions options) {
setFiles(new FilePayload[]{files}, options);
}
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
@@ -20,6 +20,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -28,10 +29,9 @@ import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.Frame.LoadState.*;
import static com.microsoft.playwright.options.LoadState.*;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class FrameImpl extends ChannelOwner implements Frame {
private String name;
@@ -88,6 +88,33 @@ public class FrameImpl extends ChannelOwner implements Frame {
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
}
@Override
public List<String> selectOption(String selector, String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, ElementHandle value, SelectOptionOptions options) {
ElementHandle[] values = value == null ? null : new ElementHandle[]{value};
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(selector, new SelectOption[0], options);
}
return selectOption(selector, Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
}
@Override
public List<String> selectOption(String selector, SelectOption value, SelectOptionOptions options) {
SelectOption[] values = value == null ? null : new SelectOption[]{value};
return selectOption(selector, values, options);
}
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
@@ -112,7 +139,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -128,7 +154,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -217,17 +242,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("click", params);
}
@@ -251,17 +265,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("dblclick", params);
}
@@ -290,7 +293,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(expression));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -306,7 +308,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
@@ -382,10 +383,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("url", url);
if (options.waitUntil != null) {
params.remove("waitUntil");
params.addProperty("waitUntil", toProtocol(options.waitUntil));
}
JsonElement result = sendMessage("goto", params);
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) {
@@ -564,11 +561,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public List<String> selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
@@ -602,31 +599,22 @@ public class FrameImpl extends ChannelOwner implements Frame {
return parseStringList(json.getAsJsonArray("values"));
}
static String toProtocol(LoadState waitUntil) {
if (waitUntil == null) {
waitUntil = LoadState.LOAD;
}
switch (waitUntil) {
case DOMCONTENTLOADED: return "domcontentloaded";
case LOAD: return "load";
case NETWORKIDLE: return "networkidle";
default: throw new PlaywrightException("Unexpected value: " + waitUntil);
}
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Frame.setContent", () -> setContentImpl(html, options));
}
@Override
public void setInputFiles(String selector, Path files, SetInputFilesOptions options) {
setInputFiles(selector, new Path[] {files}, options);
}
void setContentImpl(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
params.remove("waitUntil");
params.addProperty("waitUntil", toProtocol(options.waitUntil));
sendMessage("setContent", params);
}
@@ -635,16 +623,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
@Override
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
setInputFiles(selector, new FilePayload[]{files}, options);
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
setInputFiles(selector, Utils.toFilePayloads(files), options);
}
@Override
public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -663,10 +656,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
params.addProperty("selector", selector);
sendMessage("tap", params);
}
@@ -740,7 +729,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("waitForFunction", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
@@ -867,7 +855,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
@@ -876,22 +864,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
options = new WaitForNavigationOptions();
}
if (options.waitUntil == null) {
options.waitUntil = LOAD;
options.waitUntil = WaitUntilState.LOAD;
}
List<Waitable<Response>> waitables = new ArrayList<>();
UrlMatcher matcher = UrlMatcher.forOneOf(options.glob, options.pattern, options.predicate);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
UrlMatcher matcher = UrlMatcher.forOneOf(options.url);
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
private static String toProtocol(WaitForSelectorOptions.State state) {
return state.toString().toLowerCase();
}
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Frame.waitForSelector", () -> waitForSelectorImpl(selector, options));
@@ -903,10 +887,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
if (options.state != null) {
params.remove("state");
params.addProperty("state", toProtocol(options.state));
}
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
@@ -25,7 +25,6 @@ import java.util.HashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class JSHandleImpl extends ChannelOwner implements JSHandle {
private String preview;
@@ -51,7 +50,6 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -65,7 +63,6 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
@@ -16,9 +16,9 @@
package com.microsoft.playwright.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;
class LoggingSupport {
@@ -28,12 +28,8 @@ class LoggingSupport {
isEnabled = (debug != null) && debug.contains("pw:api");
}
private static final SimpleDateFormat timestampFormat;
static {
timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
timestampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
void withLogging(String apiName, Runnable code) {
withLogging(apiName, () -> {
@@ -60,7 +56,7 @@ class LoggingSupport {
private void logApi(String message) {
// This matches log format produced by the server.
String timestamp = timestampFormat.format(new Date());
String timestamp = ZonedDateTime.now().format(timestampFormat);
System.err.println(timestamp + " pw:api " + message);
}
}
@@ -16,12 +16,10 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Mouse;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.toProtocol;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class MouseImpl implements Mouse {
@@ -43,10 +41,6 @@ class MouseImpl implements Mouse {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
if (options.button != null) {
params.remove("button");
params.addProperty("button", toProtocol(options.button));
}
page.sendMessage("mouseClick", params);
}
@@ -19,16 +19,22 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
@@ -37,9 +43,8 @@ public class PageImpl extends ChannelOwner implements Page {
private final FrameImpl mainFrame;
private final KeyboardImpl keyboard;
private final MouseImpl mouse;
private final AccessibilityImpl accessibility;
private final TouchscreenImpl touchscreen;
private Viewport viewport;
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
@@ -59,7 +64,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
}
};
final Map<String, Binding> bindings = new HashMap<>();
final Map<String, BindingCallback> bindings = new HashMap<>();
BrowserContextImpl ownedContext;
private boolean isClosed;
final Set<Worker> workers = new HashSet<>();
@@ -95,12 +100,11 @@ public class PageImpl extends ChannelOwner implements Page {
mainFrame.page = this;
isClosed = initializer.get("isClosed").getAsBoolean();
if (initializer.has("viewportSize")) {
viewport = gson().fromJson(initializer.get("viewportSize"), Viewport.class);
viewport = gson().fromJson(initializer.get("viewportSize"), ViewportSize.class);
}
keyboard = new KeyboardImpl(this);
mouse = new MouseImpl(this);
touchscreen = new TouchscreenImpl(this);
accessibility = new AccessibilityImpl(this);
frames.add(mainFrame);
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
}
@@ -110,10 +114,11 @@ public class PageImpl extends ChannelOwner implements Page {
if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid);
// If no action taken the dialog will stay open and execution will hang. We
// could automatically dismiss the dialog if there are no listeners but we want
// the behavior to match upstream one.
listeners.notify(EventType.DIALOG, dialog);
if (listeners.hasListeners(EventType.DIALOG)) {
listeners.notify(EventType.DIALOG, dialog);
} else {
dialog.dismiss();
}
} else if ("popup".equals(event)) {
String guid = params.getAsJsonObject("page").get("guid").getAsString();
PageImpl popup = connection.getExistingObject(guid);
@@ -144,7 +149,7 @@ public class PageImpl extends ChannelOwner implements Page {
} else if ("bindingCall".equals(event)) {
String guid = params.getAsJsonObject("binding").get("guid").getAsString();
BindingCall bindingCall = connection.getExistingObject(guid);
Binding binding = bindings.get(bindingCall.name());
BindingCallback binding = bindings.get(bindingCall.name());
if (binding == null) {
binding = browserContext.bindings.get(bindingCall.name());
}
@@ -167,7 +172,7 @@ public class PageImpl extends ChannelOwner implements Page {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (params.has("failureText")) {
request.failure = new Request.RequestFailure(params.get("failureText").getAsString());
request.failure = params.get("failureText").getAsString();
}
listeners.notify(EventType.REQUESTFAILED, request);
} else if ("requestFinished".equals(event)) {
@@ -203,22 +208,33 @@ public class PageImpl extends ChannelOwner implements Page {
handled = browserContext.routes.handle(route);
}
if (!handled) {
route.continue_();
route.resume();
}
} else if ("video".equals(event)) {
video().setRelativePath(params.get("relativePath").getAsString());
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
listeners.notify(EventType.PAGEERROR, new ErrorImpl(error));
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
listeners.notify(EventType.PAGEERROR, errorStr);
} else if ("crash".equals(event)) {
listeners.notify(EventType.CRASH, this);
} else if ("close".equals(event)) {
isClosed = true;
browserContext.pages.remove(this);
listeners.notify(EventType.CLOSE, this);
didClose();
}
}
void didClose() {
isClosed = true;
browserContext.pages.remove(this);
listeners.notify(EventType.CLOSE, this);
}
private void willAddFileChooserListener() {
if (!listeners.hasListeners(EventType.FILECHOOSER)) {
updateFileChooserInterception(true);
@@ -248,12 +264,12 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void onConsole(Consumer<ConsoleMessage> handler) {
public void onConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.add(EventType.CONSOLE, handler);
}
@Override
public void offConsole(Consumer<ConsoleMessage> handler) {
public void offConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.remove(EventType.CONSOLE, handler);
}
@@ -348,12 +364,12 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void onPageError(Consumer<Error> handler) {
public void onPageError(Consumer<String> handler) {
listeners.add(EventType.PAGEERROR, handler);
}
@Override
public void offPageError(Consumer<Error> handler) {
public void offPageError(Consumer<String> handler) {
listeners.remove(EventType.PAGEERROR, handler);
}
@@ -428,7 +444,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Page waitForClose(Runnable code, WaitForCloseOptions options) {
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
if (options == null) {
options = new WaitForCloseOptions();
}
@@ -436,9 +452,9 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public ConsoleMessage waitForConsole(Runnable code, WaitForConsoleOptions options) {
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
if (options == null) {
options = new WaitForConsoleOptions();
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.timeout);
}
@@ -452,7 +468,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Download waitForDownload(Runnable code, WaitForDownloadOptions options) {
public Download waitForDownload(WaitForDownloadOptions options, Runnable code) {
if (options == null) {
options = new WaitForDownloadOptions();
}
@@ -460,7 +476,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public FileChooser waitForFileChooser(Runnable code, WaitForFileChooserOptions options) {
public FileChooser waitForFileChooser(WaitForFileChooserOptions options, Runnable code) {
// TODO: enable/disable file chooser interception
if (options == null) {
options = new WaitForFileChooserOptions();
@@ -469,39 +485,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Frame waitForFrameAttached(Runnable code, WaitForFrameAttachedOptions options) {
if (options == null) {
options = new WaitForFrameAttachedOptions();
}
return waitForEventWithTimeout(EventType.FRAMEATTACHED, code, options.timeout);
}
@Override
public Frame waitForFrameDetached(Runnable code, WaitForFrameDetachedOptions options) {
if (options == null) {
options = new WaitForFrameDetachedOptions();
}
return waitForEventWithTimeout(EventType.FRAMEDETACHED, code, options.timeout);
}
@Override
public Frame waitForFrameNavigated(Runnable code, WaitForFrameNavigatedOptions options) {
if (options == null) {
options = new WaitForFrameNavigatedOptions();
}
return waitForEventWithTimeout(EventType.FRAMENAVIGATED, code, options.timeout);
}
@Override
public Error waitForPageError(Runnable code, WaitForPageErrorOptions options) {
if (options == null) {
options = new WaitForPageErrorOptions();
}
return waitForEventWithTimeout(EventType.PAGEERROR, code, options.timeout);
}
@Override
public Page waitForPopup(Runnable code, WaitForPopupOptions options) {
public Page waitForPopup(WaitForPopupOptions options, Runnable code) {
if (options == null) {
options = new WaitForPopupOptions();
}
@@ -509,23 +493,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Request waitForRequestFailed(Runnable code, WaitForRequestFailedOptions options) {
if (options == null) {
options = new WaitForRequestFailedOptions();
}
return waitForEventWithTimeout(EventType.REQUESTFAILED, code, options.timeout);
}
@Override
public Request waitForRequestFinished(Runnable code, WaitForRequestFinishedOptions options) {
if (options == null) {
options = new WaitForRequestFinishedOptions();
}
return waitForEventWithTimeout(EventType.REQUESTFINISHED, code, options.timeout);
}
@Override
public WebSocket waitForWebSocket(Runnable code, WaitForWebSocketOptions options) {
public WebSocket waitForWebSocket(WaitForWebSocketOptions options, Runnable code) {
if (options == null) {
options = new WaitForWebSocketOptions();
}
@@ -533,7 +501,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Worker waitForWorker(Runnable code, WaitForWorkerOptions options) {
public Worker waitForWorker(WaitForWorkerOptions options, Runnable code) {
if (options == null) {
options = new WaitForWorkerOptions();
}
@@ -579,20 +547,28 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Accessibility accessibility() {
return accessibility;
public void addInitScript(String script) {
withLogging("Page.addInitScript", () -> addInitScriptImpl(script));
}
@Override
public void addInitScript(String script, Object arg) {
public void addInitScript(Path path) {
withLogging("Page.addInitScript", () -> {
JsonObject params = new JsonObject();
// TODO: support or drop arg
params.addProperty("source", script);
sendMessage("addInitScript", params);
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
});
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params);
}
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options) {
return withLogging("Page.addScriptTag",
@@ -645,13 +621,13 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void emulateMedia(EmulateMediaParams options) {
public void emulateMedia(EmulateMediaOptions options) {
withLogging("Page.emulateMedia", () -> emulateMediaImpl(options));
}
private void emulateMediaImpl(EmulateMediaParams options) {
private void emulateMediaImpl(EmulateMediaOptions options) {
if (options == null) {
options = new EmulateMediaParams();
options = new EmulateMediaOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("emulateMedia", params);
@@ -668,11 +644,11 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void exposeBinding(String name, Binding playwrightBinding, ExposeBindingOptions options) {
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
withLogging("Page.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, Binding playwrightBinding, ExposeBindingOptions options) {
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@@ -690,9 +666,9 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void exposeFunction(String name, Function playwrightFunction) {
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
withLogging("Page.exposeFunction",
() -> exposeBindingImpl(name, (Binding.Source source, Object... args) -> playwrightFunction.call(args), null));
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
@@ -708,7 +684,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Frame frameByName(String name) {
public Frame frame(String name) {
for (Frame frame : frames) {
if (name.equals(frame.name())) {
return frame;
@@ -762,8 +738,6 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoBackOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("waitUntil");
params.addProperty("waitUntil", FrameImpl.toProtocol(options.waitUntil));
JsonObject json = sendMessage("goBack", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
@@ -781,8 +755,6 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoForwardOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("waitUntil");
params.addProperty("waitUntil", FrameImpl.toProtocol(options.waitUntil));
JsonObject json = sendMessage("goForward", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
@@ -881,6 +853,13 @@ public class PageImpl extends ChannelOwner implements Page {
});
}
@Override
public void pause() {
withLogging("BrowserContext.pause", () -> {
context().pause();
});
}
@Override
public byte[] pdf(PdfOptions options) {
return withLogging("Page.pdf", () -> pdfImpl(options));
@@ -919,8 +898,6 @@ public class PageImpl extends ChannelOwner implements Page {
options = new ReloadOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("waitUntil");
params.addProperty("waitUntil", FrameImpl.toProtocol(options.waitUntil));
JsonObject json = sendMessage("reload", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
@@ -954,35 +931,58 @@ public class PageImpl extends ChannelOwner implements Page {
});
}
private static String toProtocol(ScreenshotOptions.Type type) {
return type.toString().toLowerCase();
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("Page.screenshot", () -> screenshotImpl(options));
}
@Override
public List<String> selectOption(String selector, String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, ElementHandle value, SelectOptionOptions options) {
ElementHandle[] values = value == null ? null : new ElementHandle[]{ value };
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(selector, new SelectOption[0], options);
}
return selectOption(selector, Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
}
@Override
public List<String> selectOption(String selector, SelectOption value, SelectOptionOptions options) {
SelectOption[] values = value == null ? null : new SelectOption[]{value};
return selectOption(selector, values, options);
}
private byte[] screenshotImpl(ScreenshotOptions options) {
if (options == null) {
options = new ScreenshotOptions();
}
if (options.type == null) {
options.type = ScreenshotOptions.Type.PNG;
options.type = PNG;
if (options.path != null) {
String fileName = options.path.getFileName().toString();
int extStart = fileName.lastIndexOf('.');
if (extStart != -1) {
String extension = fileName.substring(extStart).toLowerCase();
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
options.type = ScreenshotOptions.Type.JPEG;
options.type = JPEG;
}
}
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("type");
params.addProperty("type", toProtocol(options.type));
params.remove("path");
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
@@ -994,7 +994,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public List<String> selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
}
@@ -1047,6 +1047,11 @@ public class PageImpl extends ChannelOwner implements Page {
});
}
@Override
public void setInputFiles(String selector, Path files, SetInputFilesOptions options) {
setInputFiles(selector, new Path[]{files}, options);
}
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles",
@@ -1054,7 +1059,12 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
setInputFiles(selector, new FilePayload[]{files}, options);
}
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles",
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
}
@@ -1062,7 +1072,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void setViewportSize(int width, int height) {
withLogging("Page.setViewportSize", () -> {
viewport = new Viewport(width, height);
viewport = new ViewportSize(width, height);
JsonObject params = new JsonObject();
params.add("viewportSize", gson().toJsonTree(viewport));
sendMessage("setViewportSize", params);
@@ -1151,7 +1161,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Viewport viewportSize() {
public ViewportSize viewportSize() {
return viewport;
}
@@ -1172,11 +1182,11 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withLogging("Page.waitForLoadState",
() -> mainFrame.waitForLoadStateImpl(convertViaJson(state, Frame.LoadState.class), convertViaJson(options, Frame.WaitForLoadStateOptions.class)));
() -> mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class)));
}
@Override
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Page.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
@@ -1185,9 +1195,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options != null) {
frameOptions.timeout = options.timeout;
frameOptions.waitUntil = options.waitUntil;
frameOptions.glob = options.glob;
frameOptions.pattern = options.pattern;
frameOptions.predicate = options.predicate;
frameOptions.url = options.url;
}
return mainFrame.waitForNavigationImpl(code, frameOptions);
}
@@ -1196,29 +1204,6 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.FRAMENAVIGATED, frame);
}
private static class ErrorImpl implements Error {
private final SerializedError error;
ErrorImpl(SerializedError error) {
this.error = error;
}
@Override
public String message() {
return error.error.message;
}
@Override
public String name() {
return error.error.name;
}
@Override
public String stack() {
return error.error.stack;
}
}
private class WaitableFrameDetach extends WaitableEvent<EventType, Frame> {
WaitableFrameDetach(Frame frameArg) {
super(PageImpl.this.listeners, EventType.FRAMEDETACHED, detachedFrame -> frameArg.equals(detachedFrame));
@@ -1263,25 +1248,25 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Request waitForRequest(Runnable code, String urlGlob, WaitForRequestOptions options) {
return waitForRequest(code, toRequestPredicate(new UrlMatcher(urlGlob)), options);
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(toRequestPredicate(new UrlMatcher(urlGlob)), options, code);
}
@Override
public Request waitForRequest(Runnable code, Pattern urlPattern, WaitForRequestOptions options) {
return waitForRequest(code, toRequestPredicate(new UrlMatcher(urlPattern)), options);
public Request waitForRequest(Pattern urlPattern, WaitForRequestOptions options, Runnable code) {
return waitForRequest(toRequestPredicate(new UrlMatcher(urlPattern)), options, code);
}
@Override
public Request waitForRequest(Runnable code, Predicate<Request> predicate, WaitForRequestOptions options) {
return withLogging("Page.waitForRequest", () -> waitForRequestImpl(code, predicate, options));
public Request waitForRequest(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
return withLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
}
private static Predicate<Request> toRequestPredicate(UrlMatcher matcher) {
return request -> matcher.test(request.url());
}
private Request waitForRequestImpl(Runnable code, Predicate<Request> predicate, WaitForRequestOptions options) {
private Request waitForRequestImpl(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
if (options == null) {
options = new WaitForRequestOptions();
}
@@ -1294,25 +1279,25 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Response waitForResponse(Runnable code, String urlGlob, WaitForResponseOptions options) {
return waitForResponse(code, toResponsePredicate(new UrlMatcher(urlGlob)), options);
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(urlGlob)), options, code);
}
@Override
public Response waitForResponse(Runnable code, Pattern urlPattern, WaitForResponseOptions options) {
return waitForResponse(code, toResponsePredicate(new UrlMatcher(urlPattern)), options);
public Response waitForResponse(Pattern urlPattern, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(urlPattern)), options, code);
}
@Override
public Response waitForResponse(Runnable code, Predicate<Response> predicate, WaitForResponseOptions options) {
return withLogging("Page.waitForResponse", () -> waitForResponseImpl(code, predicate, options));
public Response waitForResponse(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
return withLogging("Page.waitForResponse", () -> waitForResponseImpl(predicate, options, code));
}
private static Predicate<Response> toResponsePredicate(UrlMatcher matcher) {
return response -> matcher.test(response.url());
}
private Response waitForResponseImpl(Runnable code, Predicate<Response> predicate, WaitForResponseOptions options) {
private Response waitForResponseImpl(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
if (options == null) {
options = new WaitForResponseOptions();
}
@@ -1339,4 +1324,18 @@ public class PageImpl extends ChannelOwner implements Page {
public List<Worker> workers() {
return new ArrayList<>(workers);
}
@Override
public void onceDialog(Consumer<Dialog> handler) {
onDialog(new Consumer<Dialog>() {
@Override
public void accept(Dialog dialog) {
try {
handler.accept(dialog);
} finally {
offDialog(this);
}
}
});
}
}
@@ -0,0 +1,167 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class PipeTransport implements Transport {
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
private final ReaderThread readerThread;
private final WriterThread writerThread;
private boolean isClosed;
PipeTransport(InputStream input, OutputStream output) {
DataInputStream in = new DataInputStream(new BufferedInputStream(input));
readerThread = new ReaderThread(in, incoming);
readerThread.start();
writerThread = new WriterThread(output, outgoing);
writerThread.start();
}
@Override
public void send(String message) {
if (isClosed) {
throw new PlaywrightException("Playwright connection closed");
}
try {
outgoing.put(message);
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to send message", e);
}
}
@Override
public String poll(Duration timeout) {
if (isClosed) {
throw new PlaywrightException("Playwright connection closed");
}
try {
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to read message", e);
}
}
@Override
public void close() throws IOException {
if (isClosed) {
return;
}
isClosed = true;
// We interrupt only the outgoing pipe and keep reader thread running as
// otherwise child process may block on writing to its stdout and never
// exit (observed on Windows).
readerThread.isClosing = true;
writerThread.out.close();
writerThread.interrupt();
}
}
class ReaderThread extends Thread {
private final DataInputStream in;
private final BlockingQueue<String> queue;
volatile boolean isClosing;
private static int readIntLE(DataInputStream in) throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0) {
throw new EOFException();
} else {
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
}
}
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
this.in = in;
this.queue = queue;
}
@Override
public void run() {
while (!isInterrupted()) {
try {
queue.put(readMessage());
} catch (IOException e) {
if (!isInterrupted() && !isClosing) {
e.printStackTrace();
}
break;
} catch (InterruptedException e) {
break;
}
}
}
private String readMessage() throws IOException {
int len = readIntLE(in);
byte[] raw = new byte[len];
in.readFully(raw, 0, len);
return new String(raw, StandardCharsets.UTF_8);
}
}
class WriterThread extends Thread {
final OutputStream out;
private final BlockingQueue<String> queue;
private static void writeIntLE(OutputStream out, int v) throws IOException {
out.write(v >>> 0 & 255);
out.write(v >>> 8 & 255);
out.write(v >>> 16 & 255);
out.write(v >>> 24 & 255);
}
WriterThread(OutputStream out, BlockingQueue<String> queue) {
this.out = out;
this.queue = queue;
}
@Override
public void run() {
while (!isInterrupted()) {
try {
if (queue.isEmpty())
out.flush();
sendMessage(queue.take());
} catch (IOException e) {
if (!isInterrupted())
e.printStackTrace();
break;
} catch (InterruptedException e) {
break;
}
}
}
private void sendMessage(String message) throws IOException {
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
writeIntLE(out, bytes.length);
out.write(bytes);
}
}
@@ -16,20 +16,13 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.DeviceDescriptor;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PlaywrightImpl extends ChannelOwner implements Playwright {
@@ -42,7 +35,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
// pb.environment().put("DEBUG", "pw:pro*");
Process p = pb.start();
Connection connection = new Connection(p.getInputStream(), p.getOutputStream());
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
result.driverProcess = p;
return result;
@@ -54,24 +47,15 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
private final Selectors selectors;
private final Map<String, DeviceDescriptor> devices = new HashMap<>();
final SharedSelectors sharedSelectors = new SharedSelectors();;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
selectors = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
Gson gson = Serialization.gson();
for (JsonElement item : initializer.getAsJsonArray("deviceDescriptors")) {
JsonObject o = item.getAsJsonObject();
String name = o.get("name").getAsString();
DeviceDescriptorImpl descriptor = gson.fromJson(o.get("descriptor"), DeviceDescriptorImpl.class);
descriptor.playwright = this;
devices.put(name, descriptor);
}
SelectorsImpl channel = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
sharedSelectors.addChannel(channel);
}
@Override
@@ -89,23 +73,25 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
return webkit;
}
@Override
public Map<String, DeviceDescriptor> devices() {
return devices;
}
@Override
public Selectors selectors() {
return selectors;
return sharedSelectors;
}
@Override
public void close() throws Exception {
connection.close();
// playwright-cli will exit when its stdin is closed, we wait for that.
boolean didClose = driverProcess.waitFor(30, TimeUnit.SECONDS);
if (!didClose) {
System.err.println("WARNING: Timed out while waiting for driver process to exit");
public void close() {
try {
connection.close();
// playwright-cli will exit when its stdin is closed, we wait for that.
boolean didClose = driverProcess.waitFor(30, TimeUnit.SECONDS);
if (!didClose) {
System.err.println("WARNING: Timed out while waiting for driver process to exit");
}
} catch (IOException e) {
throw new PlaywrightException("Failed to terminate", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PlaywrightException("Operation interrupted", e);
}
}
}
@@ -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.impl;
import com.google.gson.JsonObject;
public class RemoteBrowser extends ChannelOwner {
RemoteBrowser(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
BrowserImpl browser() {
return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
}
SelectorsImpl selectors() {
return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
}
}
@@ -21,6 +21,7 @@ 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.Timing;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@@ -32,8 +33,8 @@ public class RequestImpl extends ChannelOwner implements Request {
private RequestImpl redirectedFrom;
private RequestImpl redirectedTo;
final Map<String, String> headers = new HashMap<>();
RequestFailure failure;
RequestTiming timing;
String failure;
Timing timing;
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -54,7 +55,7 @@ public class RequestImpl extends ChannelOwner implements Request {
}
@Override
public RequestFailure failure() {
public String failure() {
return failure;
}
@@ -118,8 +119,8 @@ public class RequestImpl extends ChannelOwner implements Request {
}
@Override
public RequestTiming timing() {
return null;
public Timing timing() {
return timing;
}
@Override
@@ -21,6 +21,7 @@ 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.Timing;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@@ -45,7 +46,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
JsonObject item = e.getAsJsonObject();
request.headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
request.timing = Serialization.gson().fromJson(initializer.get("timing"), Request.RequestTiming.class);
request.timing = Serialization.gson().fromJson(initializer.get("timing"), Timing.class);
}
@Override
@@ -16,16 +16,15 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Route;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -44,13 +43,13 @@ public class RouteImpl extends ChannelOwner implements Route {
}
@Override
public void continue_(ContinueOptions options) {
withLogging("Route.continue", () -> continueImpl(options));
public void resume(ResumeOptions options) {
withLogging("Route.resume", () -> resumeImpl(options));
}
private void continueImpl(ContinueOptions options) {
private void resumeImpl(ResumeOptions options) {
if (options == null) {
options = new ContinueOptions();
options = new ResumeOptions();
}
JsonObject params = new JsonObject();
if (options.url != null) {
@@ -63,7 +62,15 @@ public class RouteImpl extends ChannelOwner implements Route {
params.add("headers", Serialization.toProtocol(options.headers));
}
if (options.postData != null) {
String base64 = Base64.getEncoder().encodeToString(options.postData);
byte[] bytes = null;
if (options.postData instanceof byte[]) {
bytes = (byte[]) options.postData;
} else if (options.postData instanceof String) {
bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8);
} else {
throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName());
}
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
sendMessage("continue", params);
@@ -16,12 +16,10 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Route;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -27,34 +27,18 @@ import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
import static java.nio.charset.StandardCharsets.UTF_8;
class SelectorsImpl extends ChannelOwner implements Selectors {
class SelectorsImpl extends ChannelOwner {
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void register(String name, String script, RegisterOptions options) {
withLogging("Selectors.register", () -> registerImpl(name, script, options));
}
private void registerImpl(String name, String script, RegisterOptions options) {
void registerImpl(String name, String script, Selectors.RegisterOptions options) {
if (options == null) {
options = new RegisterOptions();
options = new Selectors.RegisterOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
params.addProperty("source", script);
sendMessage("register", params);
}
@Override
public void register(String name, Path path, RegisterOptions options) {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
register(name, new String(buffer, UTF_8), options);
}
}
@@ -20,6 +20,7 @@ import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.io.ByteArrayOutputStream;
@@ -36,9 +37,15 @@ class Serialization {
static Gson gson() {
if (gson == null) {
gson = new GsonBuilder()
.registerTypeAdapter(BrowserContext.SameSite.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
.registerTypeAdapter(Page.EmulateMediaParams.Media.class, new MediaSerializer())
.registerTypeAdapter(Media.class, new MediaSerializer())
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
.registerTypeAdapter(Optional.class, new OptionalSerializer())
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeHierarchyAdapter(Map.class, new StringMapSerializer())
@@ -180,39 +187,29 @@ class Serialization {
throw new PlaywrightException("Unexpected result: " + gson().toJson(value));
}
static String toProtocol(Mouse.Button button) {
switch (button) {
case LEFT:
return "left";
case RIGHT:
return "right";
case MIDDLE:
return "middle";
default:
throw new PlaywrightException("Unexpected value: " + button);
private static class KeyboardModifiersSerializer implements JsonSerializer<List<KeyboardModifier>> {
@Override
public JsonArray serialize(List<KeyboardModifier> modifiers, Type typeOfSrc, JsonSerializationContext context) {
JsonArray result = new JsonArray();
if (modifiers.contains(KeyboardModifier.ALT)) {
result.add("Alt");
}
if (modifiers.contains(KeyboardModifier.CONTROL)) {
result.add("Control");
}
if (modifiers.contains(KeyboardModifier.META)) {
result.add("Meta");
}
if (modifiers.contains(KeyboardModifier.SHIFT)) {
result.add("Shift");
}
return result;
}
}
static JsonArray toProtocol(Set<Keyboard.Modifier> modifiers) {
JsonArray result = new JsonArray();
if (modifiers.contains(Keyboard.Modifier.ALT)) {
result.add("Alt");
}
if (modifiers.contains(Keyboard.Modifier.CONTROL)) {
result.add("Control");
}
if (modifiers.contains(Keyboard.Modifier.META)) {
result.add("Meta");
}
if (modifiers.contains(Keyboard.Modifier.SHIFT)) {
result.add("Shift");
}
return result;
}
static JsonArray toJsonArray(FileChooser.FilePayload[] files) {
static JsonArray toJsonArray(FilePayload[] files) {
JsonArray jsonFiles = new JsonArray();
for (FileChooser.FilePayload p : files) {
for (FilePayload p : files) {
JsonObject jsonFile = new JsonObject();
jsonFile.addProperty("name", p.name);
jsonFile.addProperty("mimeType", p.mimeType);
@@ -253,13 +250,14 @@ class Serialization {
private static class OptionalSerializer implements JsonSerializer<Optional<?>> {
private static boolean isSupported(Type type) {
return new TypeToken<Optional<Page.EmulateMediaParams.Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName());
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
}
@Override
public JsonElement serialize(Optional<?> src, Type typeOfSrc, JsonSerializationContext context) {
assert isSupported(typeOfSrc);
assert isSupported(typeOfSrc) : "Unexpected optional type: " + typeOfSrc.getTypeName();
if (!src.isPresent()) {
return new JsonPrimitive("null");
}
@@ -286,9 +284,9 @@ class Serialization {
}
}
private static class MediaSerializer implements JsonSerializer<Page.EmulateMediaParams.Media> {
private static class MediaSerializer implements JsonSerializer<Media> {
@Override
public JsonElement serialize(Page.EmulateMediaParams.Media src, Type typeOfSrc, JsonSerializationContext context) {
public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString().toLowerCase());
}
}
@@ -300,9 +298,16 @@ class Serialization {
}
}
private static class SameSiteAdapter extends TypeAdapter<BrowserContext.SameSite> {
private static class ToLowerCaseSerializer<E extends Enum<E>> implements JsonSerializer<E> {
@Override
public void write(JsonWriter out, BrowserContext.SameSite value) throws IOException {
public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString().toLowerCase());
}
}
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
@Override
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {
String stringValue;
switch (value) {
case STRICT:
@@ -321,9 +326,9 @@ class Serialization {
}
@Override
public BrowserContext.SameSite read(JsonReader in) throws IOException {
public SameSiteAttribute read(JsonReader in) throws IOException {
String value = in.nextString();
return BrowserContext.SameSite.valueOf(value.toUpperCase());
return SameSiteAttribute.valueOf(value.toUpperCase());
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.io.PrintStream;
import java.io.PrintWriter;
class ServerException extends PlaywrightException {
private final SerializedError.Error error;
ServerException(SerializedError.Error error) {
super(error.name + ": " + error.message);
this.error = error;
}
@Override
public void printStackTrace(PrintWriter s) {
super.printStackTrace(s);
s.println("Caused by Playwright server error:");
s.println(getMessage());
s.println(error.stack);
}
@Override
public void printStackTrace(PrintStream s) {
super.printStackTrace(s);
s.println("Caused by Playwright server error:");
s.println(getMessage());
s.println(error.stack);
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SharedSelectors extends LoggingSupport implements Selectors {
private final List<SelectorsImpl> channels = new ArrayList<>();
private final List<Registration> registrations = new ArrayList<>();
private static class Registration {
final String name;
final String script;
final RegisterOptions options;
Registration(String name, String script, RegisterOptions options) {
this.name = name;
this.script = script;
this.options = options;
}
}
@Override
public void register(String name, String script, RegisterOptions options) {
withLogging("Selectors.register", () -> registerImpl(name, script, options));
}
@Override
public void register(String name, Path path, RegisterOptions options) {
withLogging("Selectors.register", () -> {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
});
}
void addChannel(SelectorsImpl channel) {
registrations.forEach(r -> channel.registerImpl(r.name, r.script, r.options));
channels.add(channel);
}
void removeChannel(SelectorsImpl channel) {
channels.remove(channel);
}
private void registerImpl(String name, String script, RegisterOptions options) {
channels.forEach(impl -> impl.registerImpl(name, script, options));
registrations.add(new Registration(name, script, options));
}
}
@@ -13,152 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class Transport {
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
private final ReaderThread readerThread;
private final WriterThread writerThread;
private boolean isClosed;
Transport(InputStream input, OutputStream output) {
DataInputStream in = new DataInputStream(new BufferedInputStream(input));
readerThread = new ReaderThread(in, incoming);
readerThread.start();
writerThread = new WriterThread(output, outgoing);
writerThread.start();
}
public void send(String message) {
if (isClosed) {
throw new PlaywrightException("Playwright connection closed");
}
try {
outgoing.put(message);
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to send message", e);
}
}
public String poll(Duration timeout) {
if (isClosed) {
throw new PlaywrightException("Playwright connection closed");
}
try {
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to read message", e);
}
}
void close() throws IOException {
if (isClosed) {
return;
}
isClosed = true;
// We interrupt only the outgoing pipe and keep reader thread running as
// otherwise child process may block on writing to its stdout and never
// exit (observed on Windows).
readerThread.isClosing = true;
writerThread.out.close();
writerThread.interrupt();
}
}
class ReaderThread extends Thread {
private final DataInputStream in;
private final BlockingQueue<String> queue;
volatile boolean isClosing;
private static int readIntLE(DataInputStream in) throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0) {
throw new EOFException();
} else {
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
}
}
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
this.in = in;
this.queue = queue;
}
@Override
public void run() {
while (!isInterrupted()) {
try {
queue.put(readMessage());
} catch (IOException e) {
if (!isInterrupted() && !isClosing) {
e.printStackTrace();
}
break;
} catch (InterruptedException e) {
break;
}
}
}
private String readMessage() throws IOException {
int len = readIntLE(in);
byte[] raw = new byte[len];
in.readFully(raw, 0, len);
return new String(raw, StandardCharsets.UTF_8);
}
}
class WriterThread extends Thread {
final OutputStream out;
private final BlockingQueue<String> queue;
private static void writeIntLE(OutputStream out, int v) throws IOException {
out.write(v >>> 0 & 255);
out.write(v >>> 8 & 255);
out.write(v >>> 16 & 255);
out.write(v >>> 24 & 255);
}
WriterThread(OutputStream out, BlockingQueue<String> queue) {
this.out = out;
this.queue = queue;
}
@Override
public void run() {
while (!isInterrupted()) {
try {
if (queue.isEmpty())
out.flush();
sendMessage(queue.take());
} catch (IOException e) {
if (!isInterrupted())
e.printStackTrace();
break;
} catch (InterruptedException e) {
break;
}
}
}
private void sendMessage(String message) throws IOException {
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
writeIntLE(out, bytes.length);
out.write(bytes);
}
public interface Transport {
void send(String message);
String poll(Duration timeout);
void close() throws IOException;
}
@@ -16,6 +16,8 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -34,25 +36,20 @@ class UrlMatcher {
return new UrlMatcher(null, null);
}
static UrlMatcher forOneOf(String glob, Pattern pattern, Predicate<String> predicate) {
UrlMatcher result = UrlMatcher.any();
int conditionCount = 0;
if (glob != null) {
conditionCount += 1;
result = new UrlMatcher(glob);
static UrlMatcher forOneOf(Object object) {
if (object == null) {
return UrlMatcher.any();
}
if (pattern != null) {
conditionCount += 1;
result = new UrlMatcher(pattern);
if (object instanceof String) {
return new UrlMatcher((String) object);
}
if (predicate != null) {
conditionCount += 1;
result = new UrlMatcher(predicate);
if (object instanceof Pattern) {
return new UrlMatcher((Pattern) object);
}
if (conditionCount > 1) {
throw new IllegalArgumentException("Only one of glob, pattern and predicate can be specified");
if (object instanceof Predicate) {
return new UrlMatcher((Predicate<String>) object);
}
return result;
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
}
UrlMatcher(String url) {
@@ -18,13 +18,13 @@ package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@@ -114,8 +114,8 @@ class Utils {
return mimeType;
}
static FileChooser.FilePayload[] toFilePayloads(Path[] files) {
List<FileChooser.FilePayload> payloads = new ArrayList<>();
static FilePayload[] toFilePayloads(Path[] files) {
List<FilePayload> payloads = new ArrayList<>();
for (Path file : files) {
byte[] buffer;
try {
@@ -123,13 +123,13 @@ class Utils {
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file", e);
}
payloads.add(new FileChooser.FilePayload(file.getFileName().toString(), mimeType(file), buffer));
payloads.add(new FilePayload(file.getFileName().toString(), mimeType(file), buffer));
}
return payloads.toArray(new FileChooser.FilePayload[0]);
return payloads.toArray(new FilePayload[0]);
}
static void writeToFile(byte[] buffer, Path path) {
Path dir = path.getParent();
static void mkParentDirs(Path file) {
Path dir = file.getParent();
if (dir != null) {
if (!Files.exists(dir)) {
try {
@@ -139,13 +139,30 @@ class Utils {
}
}
}
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(path.toFile()));) {
}
static void writeToFile(byte[] buffer, Path path) {
mkParentDirs(path);
try (FileOutputStream out = new FileOutputStream(path.toFile())) {
out.write(buffer);
} catch (IOException e) {
throw new PlaywrightException("Failed to write to file", e);
}
}
static void writeToFile(InputStream inputStream, Path path) {
mkParentDirs(path);
try (FileOutputStream out = new FileOutputStream(path.toFile())) {
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) > 0) {
out.write(buf, 0, length);
}
} catch (IOException e) {
throw new PlaywrightException("Failed to write to file", e);
}
}
static boolean isSafeCloseError(PlaywrightException exception) {
return isSafeCloseError(exception.getMessage());
}
@@ -16,8 +16,6 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Event;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -17,6 +17,7 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
class WaitableResult<T> implements Waitable<T> {
private T result;
@@ -47,6 +48,9 @@ class WaitableResult<T> implements Waitable<T> {
@Override
public T get() {
if (exception != null) {
if (exception instanceof TimeoutError) {
throw new TimeoutError(exception.getMessage(), exception);
}
throw new PlaywrightException(exception.getMessage(), exception);
}
return result;
@@ -16,7 +16,7 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
class WaitableTimeout<T> implements Waitable<T> {
private final long deadline;
@@ -38,7 +38,7 @@ class WaitableTimeout<T> implements Waitable<T> {
if (timeoutStr.endsWith(".0")) {
timeoutStr = timeoutStr.substring(0, timeoutStr.length() - 2);
}
throw new PlaywrightException("Timeout " + timeoutStr + "ms exceeded");
throw new TimeoutError("Timeout " + timeoutStr + "ms exceeded");
}
@Override
@@ -17,11 +17,12 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.WebSocket;
import com.microsoft.playwright.WebSocketFrame;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
@@ -54,22 +55,22 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
@Override
public void onFrameReceived(Consumer<FrameData> handler) {
public void onFrameReceived(Consumer<WebSocketFrame> handler) {
listeners.add(EventType.FRAMERECEIVED, handler);
}
@Override
public void offFrameReceived(Consumer<FrameData> handler) {
public void offFrameReceived(Consumer<WebSocketFrame> handler) {
listeners.remove(EventType.FRAMERECEIVED, handler);
}
@Override
public void onFrameSent(Consumer<FrameData> handler) {
public void onFrameSent(Consumer<WebSocketFrame> handler) {
listeners.add(EventType.FRAMESENT, handler);
}
@Override
public void offFrameSent(Consumer<FrameData> handler) {
public void offFrameSent(Consumer<WebSocketFrame> handler) {
listeners.remove(EventType.FRAMESENT, handler);
}
@@ -84,7 +85,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
@Override
public FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options) {
public WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable code) {
if (options == null) {
options = new WaitForFrameReceivedOptions();
}
@@ -92,21 +93,13 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
@Override
public FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options) {
public WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable code) {
if (options == null) {
options = new WaitForFrameSentOptions();
}
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.timeout);
}
@Override
public String waitForSocketError(Runnable code, WaitForSocketErrorOptions options) {
if (options == null) {
options = new WaitForSocketErrorOptions();
}
return waitForEventWithTimeout(EventType.SOCKETERROR, code, options.timeout);
}
@Override
public boolean isClosed() {
return isClosed;
@@ -149,10 +142,10 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
return runUntil(code, new WaitableRace<>(waitables));
}
private static class FrameDataImpl implements FrameData {
private static class WebSocketFrameImpl implements WebSocketFrame {
private final byte[] bytes;
FrameDataImpl(String payload, boolean isBase64) {
WebSocketFrameImpl(String payload, boolean isBase64) {
if (isBase64) {
bytes = Base64.getDecoder().decode(payload);
} else {
@@ -161,7 +154,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
@Override
public byte[] body() {
public byte[] binary() {
return bytes;
}
@@ -175,15 +168,15 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
void handleEvent(String event, JsonObject parameters) {
switch (event) {
case "frameSent": {
FrameDataImpl frameData = new FrameDataImpl(
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
listeners.notify(EventType.FRAMESENT, frameData);
listeners.notify(EventType.FRAMESENT, WebSocketFrame);
break;
}
case "frameReceived": {
FrameDataImpl frameData = new FrameDataImpl(
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
listeners.notify(EventType.FRAMERECEIVED, frameData);
listeners.notify(EventType.FRAMERECEIVED, WebSocketFrame);
break;
}
case "socketError": {
@@ -0,0 +1,119 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
class WebSocketTransport implements Transport {
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
private final ClientConnection clientConnection;
private boolean isClosed;
private volatile Exception lastError;
ListenerCollection<EventType> listeners = new ListenerCollection<>();
private enum EventType { CLOSE }
private class ClientConnection extends WebSocketClient {
ClientConnection(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake handshakedata) {
}
@Override
public void onMessage(String message) {
incoming.add(message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
}
@Override
public void onError(Exception ex) {
lastError = ex;
}
}
WebSocketTransport(URI uri, Duration timeout) {
clientConnection = new ClientConnection(uri);
try {
if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
throw new PlaywrightException("Failed to connect", lastError);
}
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to connect", e);
}
}
@Override
public void send(String message) {
checkIfClosed();
clientConnection.send(message);
}
@Override
public String poll(Duration timeout) {
checkIfClosed();
try {
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to read message", e);
}
}
@Override
public void close() throws IOException {
if (isClosed) {
return;
}
isClosed = true;
clientConnection.close();
}
void onClose(Consumer<WebSocketTransport> handler) {
listeners.add(EventType.CLOSE, handler);
}
void offClose(Consumer<WebSocketTransport> handler) {
listeners.remove(EventType.CLOSE, handler);
}
private void checkIfClosed() {
if (isClosed) {
throw new PlaywrightException("Playwright connection closed");
}
if (clientConnection.isClosed()) {
isClosed = true;
listeners.notify(EventType.CLOSE, this);
throw new PlaywrightException("Playwright connection closed");
}
}
}
@@ -16,17 +16,16 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Worker;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
class WorkerImpl extends ChannelOwner implements Worker {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
@@ -59,7 +58,7 @@ class WorkerImpl extends ChannelOwner implements Worker {
}
@Override
public Worker waitForClose(Runnable code, WaitForCloseOptions options) {
public Worker waitForClose(WaitForCloseOptions options, Runnable code) {
if (options == null) {
options = new WaitForCloseOptions();
}
@@ -71,7 +70,6 @@ class WorkerImpl extends ChannelOwner implements Worker {
return withLogging("Worker.evaluate", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -84,7 +82,6 @@ class WorkerImpl extends ChannelOwner implements Worker {
return withLogging("Worker.evaluateHandle", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
@@ -0,0 +1,31 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.Page;
public interface BindingCallback {
interface Source {
BrowserContext context();
Page page();
Frame frame();
}
Object call(Source source, Object... args);
}
@@ -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 BoundingBox {
/**
* the x coordinate of the element in pixels.
*/
public double x;
/**
* the y coordinate of the element in pixels.
*/
public double y;
/**
* the width of the element in pixels.
*/
public double width;
/**
* the height of the element in pixels.
*/
public double height;
}
@@ -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 Clip {
/**
* x-coordinate of top-left corner of clip area
*/
public double x;
/**
* y-coordinate of top-left corner of clip area
*/
public double y;
/**
* width of clipping area
*/
public double width;
/**
* height of clipping area
*/
public double height;
public Clip(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
@@ -0,0 +1,23 @@
/*
* 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 ColorScheme {
LIGHT,
DARK,
NO_PREFERENCE
}
@@ -0,0 +1,83 @@
/*
* 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 Cookie {
public String name;
public String value;
/**
* either url or domain / path are required. Optional.
*/
public String url;
/**
* either url or domain / path are required Optional.
*/
public String domain;
/**
* either url or domain / path are required Optional.
*/
public String path;
/**
* Unix time in seconds. Optional.
*/
public Double expires;
/**
* Optional.
*/
public Boolean httpOnly;
/**
* Optional.
*/
public Boolean secure;
/**
* Optional.
*/
public SameSiteAttribute sameSite;
public Cookie(String name, String value) {
this.name = name;
this.value = value;
}
public Cookie setUrl(String url) {
this.url = url;
return this;
}
public Cookie setDomain(String domain) {
this.domain = domain;
return this;
}
public Cookie setPath(String path) {
this.path = path;
return this;
}
public Cookie setExpires(double expires) {
this.expires = expires;
return this;
}
public Cookie setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
public Cookie setSecure(boolean secure) {
this.secure = secure;
return this;
}
public Cookie setSameSite(SameSiteAttribute sameSite) {
this.sameSite = sameSite;
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 enum ElementState {
VISIBLE,
HIDDEN,
STABLE,
ENABLED,
DISABLED,
EDITABLE
}
@@ -0,0 +1,38 @@
/*
* 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 FilePayload {
/**
* [File] name
*/
public String name;
/**
* [File] type
*/
public String mimeType;
/**
* File content
*/
public byte[] buffer;
public FilePayload(String name, String mimeType, byte[] buffer) {
this.name = name;
this.mimeType = mimeType;
this.buffer = buffer;
}
}
@@ -14,9 +14,8 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.options;
public interface Event<EventType> {
EventType type();
Object data();
public interface FunctionCallback {
Object call(Object... args);
}
@@ -14,36 +14,28 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.options;
public class Geolocation {
/**
* Latitude between -90 and 90.
*/
public double latitude;
/**
* Longitude between -180 and 180.
*/
public double longitude;
/**
* Non-negative accuracy value. Defaults to {@code 0}.
*/
public Double accuracy;
public Geolocation() {
}
public Geolocation(double latitude, double longitude) {
this(latitude, longitude, null);
}
public Geolocation(double latitude, double longitude, Double accuracy) {
this.latitude = latitude;
this.longitude = longitude;
this.accuracy = accuracy;
}
public Geolocation withLatitude(double latitude) {
this.latitude = latitude;
return this;
}
public Geolocation withLongitude(double longitude) {
this.longitude = longitude;
return this;
}
public Geolocation withAccuracy(double accuracy) {
public Geolocation setAccuracy(double accuracy) {
this.accuracy = accuracy;
return this;
}
}
}
@@ -0,0 +1,27 @@
/*
* 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 HttpCredentials {
public String username;
public String password;
public HttpCredentials(String username, String password) {
this.username = username;
this.password = password;
}
}
@@ -0,0 +1,24 @@
/*
* 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 KeyboardModifier {
ALT,
CONTROL,
META,
SHIFT
}
@@ -0,0 +1,23 @@
/*
* 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 LoadState {
LOAD,
DOMCONTENTLOADED,
NETWORKIDLE
}
@@ -0,0 +1,53 @@
/*
* 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 Margin {
/**
* Top margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String top;
/**
* Right margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String right;
/**
* Bottom margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String bottom;
/**
* Left margin, accepts values labeled with units. Defaults to {@code 0}.
*/
public String left;
public Margin setTop(String top) {
this.top = top;
return this;
}
public Margin setRight(String right) {
this.right = right;
return this;
}
public Margin setBottom(String bottom) {
this.bottom = bottom;
return this;
}
public Margin setLeft(String left) {
this.left = left;
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 Media {
SCREEN,
PRINT
}
@@ -0,0 +1,23 @@
/*
* 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 MouseButton {
LEFT,
RIGHT,
MIDDLE
}
@@ -14,26 +14,14 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.options;
public class Position {
public int x;
public int y;
public double x;
public double y;
public Position() {
}
public Position(int x, int y) {
public Position(double x, double y) {
this.x = x;
this.y = y;
}
public Position withX(int x) {
this.x = x;
return this;
}
public Position withY(int y) {
this.y = y;
return this;
}
}
}
@@ -0,0 +1,53 @@
/*
* 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 Proxy {
/**
* Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example {@code http://myproxy.com:3128} or
* {@code socks5://myproxy.com:3128}. Short form {@code myproxy.com:3128} is considered an HTTP proxy.
*/
public String server;
/**
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
*/
public String bypass;
/**
* Optional username to use if HTTP proxy requires authentication.
*/
public String username;
/**
* Optional password to use if HTTP proxy requires authentication.
*/
public String password;
public Proxy(String server) {
this.server = server;
}
public Proxy setBypass(String bypass) {
this.bypass = bypass;
return this;
}
public Proxy setUsername(String username) {
this.username = username;
return this;
}
public Proxy setPassword(String password) {
this.password = password;
return this;
}
}
@@ -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.options;
public class RecordVideoSize {
/**
* Video frame width.
*/
public int width;
/**
* Video frame height.
*/
public int height;
public RecordVideoSize(int width, int height) {
this.width = width;
this.height = height;
}
}
@@ -0,0 +1,23 @@
/*
* 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 SameSiteAttribute {
STRICT,
LAX,
NONE
}
@@ -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 ScreenshotType {
PNG,
JPEG
}
@@ -0,0 +1,45 @@
/*
* 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 SelectOption {
/**
* Matches by {@code option.value}. Optional.
*/
public String value;
/**
* Matches by {@code option.label}. Optional.
*/
public String label;
/**
* Matches by the index. Optional.
*/
public Integer index;
public SelectOption setValue(String value) {
this.value = value;
return this;
}
public SelectOption setLabel(String label) {
this.label = label;
return this;
}
public SelectOption setIndex(int index) {
this.index = index;
return this;
}
}

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