1
0
mirror of synced 2026-05-28 05:33:16 +00:00

Compare commits

...

63 Commits

Author SHA1 Message Date
Yury Semikhatsky 0a4159f15e chore: update released version to 0.181.0 2021-02-19 09:38:58 -08:00
Yury Semikhatsky b2c00a2195 fix: update worflow configs to run on release branches (#288) 2021-02-19 09:31:36 -08:00
Yury Semikhatsky 98195b762b chore: roll 1.8.1 driver in the release branch (#287) 2021-02-19 09:22:54 -08:00
Yury Semikhatsky b9dbd56765 chore: set version to 0.180.0 (#227) 2021-01-22 17:56:03 -08:00
Yury Semikhatsky 454b155bbc chore: delete Listener interface from implementation (#226) 2021-01-22 13:35:11 -08:00
Yury Semikhatsky 808f666399 fix(api): make all event listeners accept one parameter (#225) 2021-01-22 12:49:27 -08:00
Yury Semikhatsky d1f1287e77 fix(api): update waitForRequest/Response predicate to accept Request/… (#224) 2021-01-21 18:50:04 -08:00
Yury Semikhatsky 0c30cc1bfc chore: simplify api generator (#223) 2021-01-21 18:22:02 -08:00
Yury Semikhatsky e80c23980a test(dom): convert remaining element handle convenience tests (#222) 2021-01-21 13:51:29 -08:00
Yury Semikhatsky 50a631304d feat(driver): roll driver to 1.8.0 (#221) 2021-01-21 13:35:39 -08:00
Yury Semikhatsky bfc7bcdc4b docs: add table of contents, highlight min Java version (#220) 2021-01-21 09:04:53 -08:00
Yury Semikhatsky 3376836752 fix(api): remove Listener interface from public API (#219) 2021-01-20 19:32:53 -08:00
ephung01 2ba2706104 test: add new test 'Page Keyboard' (#217) 2021-01-20 19:31:45 -08:00
Yury Semikhatsky 58e97f1b36 fix(api): delete Deferred interface, use waitFor* with callback (#218) 2021-01-20 16:33:26 -08:00
Yury Semikhatsky 6c71f15866 docs: add thread-safety notes 2021-01-19 11:38:10 -08:00
ephung01 3b495615c2 test: add new test 'Element Handle Type' and 'Element Handle Press' (#211) 2021-01-15 12:26:27 -08:00
Yury Semikhatsky ba5f8a4160 fix(logs): use same format for pw:api logs as on server (#205) 2021-01-11 22:22:58 -08:00
Yury Semikhatsky 88bd51ce74 docs: remove extra paragraphs from javadoc (#204) 2021-01-11 13:42:28 -08:00
codeboyzhou cc1057d910 fix(scripts): 'generate_api.sh' does not work on macOS (#201)
Co-authored-by: Yury Semikhatsky <yurys@chromium.org>
2021-01-11 11:31:53 -08:00
ephung01 a3e6fa320f test: add new test 'Element Handle Select Text', Fixed webkit windows check various SSL error messages base on platform and OS (#198) 2021-01-11 11:22:06 -08:00
Yury Semikhatsky 89492da7d6 fix: change some int types to double (#200) 2021-01-08 18:29:49 -08:00
Yury Semikhatsky 01d7eac7ad feat: roll driver, switch to new api.json format (#199) 2021-01-08 15:50:38 -08:00
Yury Semikhatsky 1777f1aac8 fix: do not dismiss dialogs automatically (#197) 2021-01-07 11:50:55 -08:00
ephung01 e17689e16e test: add new test 'Element Handle Query Selector' (#194) 2021-01-07 09:33:51 -08:00
codeboyzhou 75c47a88ff chore: add help section for the driver download script (#196) 2021-01-07 09:30:23 -08:00
Yury Semikhatsky c3190b152a feat(logging): support DEBUG=pw:api (#195) 2021-01-06 23:09:49 -08:00
Yury Semikhatsky fc5bd76334 fix(video): make Video.path work (#193) 2021-01-06 15:12:01 -08:00
Yury Semikhatsky 13d7ee6b44 fix: remove Logger from API (#192) 2021-01-06 14:38:46 -08:00
codeboyzhou 1ab6e6b78c fix(scripts): Dockerfile is outdated (#191) 2021-01-06 10:23:53 -08:00
Yury Semikhatsky 6a67124e1c fix: limit number of tracked unused Deferred objects to 10 (#189) 2021-01-05 15:05:12 -08:00
Yury Semikhatsky b2c27143a8 fix(api): redo waitFor* methods (#188) 2021-01-04 18:50:08 -08:00
Yury Semikhatsky 206a224bf6 fix(pdf): change type of pdf scale option to double (#187) 2021-01-04 17:56:13 -08:00
Yury Semikhatsky a318e3f261 test: add missing .get() call on the result of page.waitForSelector (#185) 2021-01-04 15:19:04 -08:00
Yury Semikhatsky eeee9c6bb1 fix: ignoreDefaultArgs and ignoreAllDefaultArgs (#184) 2021-01-04 14:54:23 -08:00
codeboyzhou e51bb075e7 fix: java main process will be suspended when browsers installation timed out (#180) 2021-01-04 10:56:57 -08:00
codeboyzhou 2ce8321178 chore: give more friendly exception info while create driver (#181) 2021-01-04 10:56:03 -08:00
Yury Semikhatsky 68a0b74215 feat: throw in Playwright.close() if .get() was not called on Deferred (#177) 2020-12-28 17:09:56 -08:00
Yury Semikhatsky aca2d94625 fix: allow configuring environment variables (#176) 2020-12-28 12:29:25 -08:00
Yury Semikhatsky cad90a3af6 fix: interrupt listener on context close, more tests (#174) 2020-12-24 11:05:03 -08:00
Yury Semikhatsky 5b90067ffc chore: roll to playwright-cli@0.180.0-next.1608746109749-cbc13bd (#172) 2020-12-23 11:23:27 -08:00
Yury Semikhatsky 8eb1c034b1 test: unflake TestGeolocation.shouldUseContextOptionsForPopup (#171) 2020-12-23 11:10:43 -08:00
Yury Semikhatsky 66334131ca chore: bump version to 0.180.0-SNAPSHOT (#170) 2020-12-23 10:59:56 -08:00
Yury Semikhatsky 0bdaa52533 chore: bump version to 0.171.1-SNAPSHOT (#168) 2020-12-22 15:50:40 -08:00
Yury Semikhatsky 9d61ceec50 fix: correctly encode unicode strings to UTF-8 (#167) 2020-12-22 15:06:48 -08:00
Yury Semikhatsky e376ce7fd1 chore: bump version to 0.171.0 (#166) 2020-12-22 13:23:08 -08:00
Yury Semikhatsky cc2d4fa707 chore: update cli to 0.171.0-1608602762905-6aa14d3 (#165) 2020-12-22 12:03:48 -08:00
Yury Semikhatsky 077e8f6daa fix: correctly initialize page.isClosed() (#163) 2020-12-21 12:43:27 -08:00
Yury Semikhatsky 190bcbdd78 docs: clarify what download script does (#162) 2020-12-21 12:04:18 -08:00
Yury Semikhatsky 7e3e1c0bc7 chore: set example encoding to utf-8 (#161) 2020-12-21 10:12:52 -08:00
Yury Semikhatsky 80d92cbc48 chore: version 0.172.4-SNAPSHOT (#159) 2020-12-19 18:55:06 -08:00
Yury Semikhatsky 7c07387121 docs: update examples to use 0.170.3 (#158) 2020-12-19 18:49:43 -08:00
Yury Semikhatsky 4303a7e963 chore: version 0.170.3 (now for realz) (#157) 2020-12-19 18:38:38 -08:00
Yury Semikhatsky bc82e739f2 chore: version 0.172.4-SNAPSHOT (#156) 2020-12-19 18:33:01 -08:00
Yury Semikhatsky d7fee058b7 chore: version 0.170.3 (#155) 2020-12-19 18:05:19 -08:00
Yury Semikhatsky 569259d3df devops: auto update browser versions in readme (#153) 2020-12-18 18:42:11 -08:00
Yury Semikhatsky ef6adb8123 fix: allow to build options from DeviceDescriptor (#151) 2020-12-18 13:45:02 -08:00
Yury Semikhatsky ec86901e97 docs: edit examples reame 2020-12-18 13:02:17 -08:00
Yury Semikhatsky 64d9c82f71 chore: add examples project (#150) 2020-12-18 12:56:36 -08:00
Yury Semikhatsky 0f130d4358 feat: make driver-bundle required for playwright (#149) 2020-12-18 12:21:01 -08:00
Yury Semikhatsky fc0c183eec docs: add Java requirements section (#148) 2020-12-18 12:01:47 -08:00
Yury Semikhatsky 259f2481bd docs: add links to javadoc.io (#147) 2020-12-17 12:29:36 -08:00
Yury Semikhatsky 96fa086667 docs: update README.md with more instructions (#146) 2020-12-17 12:08:49 -08:00
Yury Semikhatsky f073a75bbe chore: bump version to 0.170.3-SNAPSHOT (#145) 2020-12-17 10:37:05 -08:00
139 changed files with 8799 additions and 5506 deletions
+8 -8
View File
@@ -1,9 +1,15 @@
name: Test
on:
push:
branches: [ master ]
branches:
- master
- release-*
pull_request:
branches: [ master ]
branches:
- master
- release-*
jobs:
build:
timeout-minutes: 30
@@ -26,12 +32,6 @@ jobs:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Cache Downloaded Drivers
uses: actions/cache@v2
with:
path: driver-bundle/src/main/resources/driver
key: ${{ runner.os }}-drivers-${{ hashFiles('scripts/*') }}
restore-keys: ${{ runner.os }}-drivers
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
+6 -2
View File
@@ -1,12 +1,16 @@
name: Verify API
on:
push:
branches: [ master ]
branches:
- master
- release-*
paths:
- 'scripts/*'
- 'api-generator/*'
pull_request:
branches: [ master ]
branches:
- master
- release-*
paths:
- 'scripts/**'
- 'api-generator/**'
+1 -1
View File
@@ -11,7 +11,7 @@ git clone https://github.com/microsoft/playwright-java
cd playwright-java
```
2. Run the following script to download playwright-cli binaries for all platforms into `driver-bundle/src/main/resources/driver/` directory. It will also install Playwright and download browser binaries for Chromium, Firefox and WebKit.
2. Run the following script to download playwright-cli binaries for all platforms into `driver-bundle/src/main/resources/driver/` directory (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
```bash
scripts/download_driver_for_all_platforms.sh
+2 -1
View File
@@ -66,5 +66,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && ./scripts/download_driver.sh && mvn install -D skipTests --no-transfer-progress && \
RUN cd /tmp/pw-java && ./scripts/download_driver_for_all_platforms.sh && \
mvn install -D skipTests --no-transfer-progress && \
rm -rf /tmp/pw-java
+49 -19
View File
@@ -1,30 +1,57 @@
# 🎭 [Playwright](https://github.com/microsoft/playwright) for Java
# 🎭 [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)
### _The project is in early development phase, the APIs match those in typescript version of Playwright but are subject to change._
#### [Website](https://playwright.dev/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
Playwright is a Java library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->89.0.4344.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: |
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.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
- [Is Playwright thread-safe?](#is-playwright-thread-safe)
* [Examples](#examples)
- [Page screenshot](#page-screenshot)
- [Mobile and geolocation](#mobile-and-geolocation)
- [Evaluate JavaScript in browser](#evaluate-javascript-in-browser)
- [Intercept network requests](#intercept-network-requests)
* [Documentation](#documentation)
* [Contributing](#contributing)
* [Is Playwright for Java ready?](#is-playwright-for-java-ready)
## Usage
Playwright requires **Java 8** or newer.
#### Add Maven dependency
To run Playwright simply add 2 modules to your Maven project:
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add a couple of dependencies to your Maven `pom.xml` file as described below. If you're not familiar with Maven please refer to its [documentation](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
To run Playwright simply add following dependency to your Maven project:
```xml
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.170.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
<version>0.170.0</version>
<version>0.171.0</version>
</dependency>
```
#### Is Playwright thread-safe?
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create new many Playwright instances each on its own thread.
## Examples
You can find Maven project with the examples [here](./examples).
#### Page screenshot
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.
@@ -71,14 +98,10 @@ public class MobileAndGeolocation {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withViewport(pixel2.viewport().width(), pixel2.viewport().height())
.withUserAgent(pixel2.userAgent())
.withDeviceScaleFactor(pixel2.deviceScaleFactor())
.withIsMobile(pixel2.isMobile())
.withHasTouch(pixel2.hasTouch())
.withDevice(pixel2)
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
@@ -92,7 +115,7 @@ public class MobileAndGeolocation {
}
```
#### Evaluate in browser context
#### Evaluate JavaScript in browser
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
@@ -146,9 +169,16 @@ public class InterceptNetworkRequests {
}
```
## Notes
## Documentation
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install driver.
We are in the process of converting our documentation from the Node.js form to [Javadocs](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html). You can go ahead and use the Node.js [documentation](https://playwright.dev/) since the API is pretty much the same.
Original Playwright [documentation](https://playwright.dev/). We are converting it to javadoc.
## Contributing
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
## Is Playwright for Java ready?
Yes, Playwright for Java is ready. We are still not at the version v1.0, so breaking API changes could potentially happen. But a) this is unlikely and b) we will only do that if we know it improves your experience with the new library. We'd like to collect your feedback before we freeze the API for v1.0.
> Note: We don't yet support some of the edge-cases of the vendor-specific APIs such as collecting Chromium trace, coverage report, etc.
@@ -1,375 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.tools;
import java.util.HashMap;
import java.util.Map;
class Types {
interface CustomMapping {
void defineTypesIn(TypeDefinition scope);
}
class Mapping {
final String from;
final String to;
final CustomMapping customMapping;
Mapping(String from, String to) {
this(from, to, null);
}
Mapping(String from, String to, CustomMapping customMapping) {
this.from = from;
this.to = to;
this.customMapping = customMapping;
}
}
private final Map<String, Mapping> jsonPathToMapping = new HashMap<>();
Types() {
// State enums
add("Page.waitForLoadState.state", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState");
add("Frame.waitForLoadState.state", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState");
add("ElementHandle.waitForElementState.state", "\"disabled\"|\"enabled\"|\"hidden\"|\"stable\"|\"visible\"", "ElementState");
add("Logger.isEnabled.severity", "\"error\"|\"info\"|\"verbose\"|\"warning\"", "Severity");
add("Logger.log.severity", "\"error\"|\"info\"|\"verbose\"|\"warning\"", "Severity");
// Option enums
add("Browser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Browser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Page.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Page.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Page.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.emulateMedia.params.media", "null|\"print\"|\"screen\"", "Media");
add("Page.emulateMedia.params.colorScheme", "null|\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Page.goBack.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.goForward.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.goto.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.reload.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.screenshot.options.type", "\"jpeg\"|\"png\"", "Type");
add("Page.setContent.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.waitForFunction.options.polling", "number|\"raf\"", "double", new PollingOption());
add("Page.waitForNavigation.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("Frame.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Frame.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Frame.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.goto.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.setContent.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.waitForFunction.options.polling", "number|\"raf\"", "double", new PollingOption());
add("Frame.waitForNavigation.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("ElementHandle.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("ElementHandle.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("ElementHandle.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.screenshot.options.type", "\"jpeg\"|\"png\"", "Type");
add("ElementHandle.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("Mouse.click.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.down.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.up.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
// File
add("Page.addScriptTag.script.path", "string", "Path");
add("Page.addStyleTag.style.path", "string", "Path");
add("Page.pdf.options.path", "string", "Path");
add("Page.screenshot.options.path", "string", "Path");
add("Frame.addScriptTag.script.path", "string", "Path");
add("Frame.addStyleTag.style.path", "string", "Path");
add("ElementHandle.screenshot.options.path", "string", "Path");
add("Route.fulfill.response.path", "string", "Path");
add("Route.fulfill.response.status", "number", "int");
add("Browser.newContext.options.recordHar.path", "string", "Path");
add("Browser.newContext.options.recordVideo.dir", "string", "Path");
add("Browser.newPage.options.recordHar.path", "string", "Path");
add("Browser.newPage.options.recordVideo.dir", "string", "Path");
add("BrowserType.launchPersistentContext.options.recordHar.path", "string", "Path");
add("BrowserType.launchPersistentContext.options.recordVideo.dir", "string", "Path");
add("BrowserType.launchPersistentContext.userDataDir", "string", "Path");
add("BrowserType.launchPersistentContext.options.executablePath", "string", "Path");
add("BrowserType.launchServer.options.executablePath", "string", "Path");
add("BrowserType.launchPersistentContext.options.downloadsPath", "string", "Path");
add("BrowserType.launch.options.executablePath", "string", "Path");
add("BrowserType.launch.options.downloadsPath", "string", "Path");
add("BrowserContext.storageState.options.path", "string", "Path");
add("ChromiumBrowser.startTracing.options.path", "string", "Path");
// Route
add("BrowserContext.route.handler", "function(Route, Request)", "Consumer<Route>");
add("BrowserContext.unroute.handler", "function(Route, Request)", "Consumer<Route>");
add("Page.route.handler", "function(Route, Request)", "Consumer<Route>");
add("Page.unroute.handler", "function(Route, Request)", "Consumer<Route>");
// Viewport size.
add("Browser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty());
add("Browser.newPage.options.viewport", "null|Object", "Page.Viewport", new Empty());
add("Page.setViewportSize.viewportSize", "Object", "Viewport", new Empty());
add("Page.viewportSize", "null|Object", "Viewport", new Empty());
add("BrowserType.launchPersistentContext.options.viewport", "null|Object", "Page.Viewport", new Empty());
// RecordVideo size.
add("Browser.newContext.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("Browser.newPage.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("BrowserType.launchPersistentContext.recordVideo.size", "Object", "Browser.VideoSize", new Empty());
// HTTP credentials.
add("Browser.newContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("Browser.newPage.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserType.launchPersistentContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserContext.setHTTPCredentials.httpCredentials", "null|Object", "do nothing", new Empty());
// EvaluationArgument
add("Page.$eval.arg", "EvaluationArgument", "Object");
add("Page.$$eval.arg", "EvaluationArgument", "Object");
add("Page.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("Page.evaluate.arg", "EvaluationArgument", "Object");
add("Page.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Page.waitForFunction.arg", "EvaluationArgument", "Object");
add("Frame.$eval.arg", "EvaluationArgument", "Object");
add("Frame.$$eval.arg", "EvaluationArgument", "Object");
add("Frame.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("Frame.evaluate.arg", "EvaluationArgument", "Object");
add("Frame.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Frame.waitForFunction.arg", "EvaluationArgument", "Object");
add("ElementHandle.$eval.arg", "EvaluationArgument", "Object");
add("ElementHandle.$$eval.arg", "EvaluationArgument", "Object");
add("ElementHandle.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("ElementHandle.evaluate.arg", "EvaluationArgument", "Object");
add("ElementHandle.evaluateHandle.arg", "EvaluationArgument", "Object");
add("JSHandle.evaluate.arg", "EvaluationArgument", "Object");
add("JSHandle.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Worker.evaluate.arg", "EvaluationArgument", "Object");
add("Worker.evaluateHandle.arg", "EvaluationArgument", "Object");
// js functions are always passed as text in java.
add("Page.$eval.pageFunction", "function(Element)", "String");
add("Page.$$eval.pageFunction", "function(Array<Element>)", "String");
add("Frame.$eval.pageFunction", "function(Element)", "String");
add("Frame.$$eval.pageFunction", "function(Array<Element>)", "String");
add("ElementHandle.$eval.pageFunction", "function(Element)", "String");
add("ElementHandle.$$eval.pageFunction", "function(Array<Element>)", "String");
add("ElementHandle.evaluate.pageFunction", "function", "String");
add("JSHandle.evaluate.pageFunction", "function", "String");
add("BrowserContext.exposeBinding.playwrightBinding", "function", "Page.Binding");
add("BrowserContext.exposeFunction.playwrightFunction", "function", "Page.Function");
add("Page.exposeBinding.playwrightBinding", "function", "Binding");
add("Page.exposeFunction.playwrightFunction", "function", "Function");
add("BrowserContext.addInitScript.script", "function|string|Object", "String");
add("Page.addInitScript.script", "function|string|Object", "String");
add("Page.evaluate.pageFunction", "function|string", "String");
add("Page.evaluateHandle.pageFunction", "function|string", "String");
add("Page.waitForFunction.pageFunction", "function|string", "String");
add("Frame.evaluate.pageFunction", "function|string", "String");
add("Frame.evaluateHandle.pageFunction", "function|string", "String");
add("Frame.waitForFunction.pageFunction", "function|string", "String");
add("ElementHandle.evaluateHandle.pageFunction", "function|string", "String");
add("JSHandle.evaluateHandle.pageFunction", "function|string", "String");
add("Selectors.register.script", "function|string|Object", "String");
add("Worker.evaluate.pageFunction", "function|string", "String");
add("Worker.evaluateHandle.pageFunction", "function|string", "String");
add("WebSocket.waitForEvent.optionsOrPredicate", "Function|Object", "String");
// Return structures
add("Dialog.type", "string", "Type", new Empty());
add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Promise<null|Object>", "BoundingBox", new Empty());
add("Accessibility.snapshot", "Promise<null|Object>", "AccessibilityNode", new Empty());
add("WebSocket.framereceived", "Object", "FrameData", new Empty());
add("WebSocket.framesent", "Object", "FrameData", new Empty());
add("Page.waitForRequest", "Promise<Request>", "Deferred<Request>");
add("Page.waitForResponse", "Promise<Response>", "Deferred<Response>");
add("Page.waitForNavigation", "Promise<null|Response>", "Deferred<Response>");
add("Frame.waitForNavigation", "Promise<null|Response>", "Deferred<Response>");
add("Page.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("Frame.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("ElementHandle.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("Frame.waitForLoadState", "Promise", "Deferred<Void>", new Empty());
add("Page.waitForLoadState", "Promise", "Deferred<Void>", new Empty());
add("Frame.waitForTimeout", "Promise", "Deferred<Void>", new Empty());
add("Page.waitForTimeout", "Promise", "Deferred<Void>", new Empty());
add("Frame.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("Page.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("ElementHandle.waitForElementState", "Promise", "Deferred<Void>", new Empty());
// Custom options
add("Page.pdf.options.margin.top", "string|number", "String");
add("Page.pdf.options.margin.right", "string|number", "String");
add("Page.pdf.options.margin.bottom", "string|number", "String");
add("Page.pdf.options.margin.left", "string|number", "String");
add("Page.pdf.options.width", "string|number", "String");
add("Page.pdf.options.height", "string|number", "String");
add("Page.goto.options", "Object", "NavigateOptions");
add("Frame.goto.options", "Object", "NavigateOptions");
add("Page.click.options.position", "Object", "Position", new Empty());
add("Page.dblclick.options.position", "Object", "Position", new Empty());
add("Page.hover.options.position", "Object", "Position", new Empty());
add("Frame.click.options.position", "Object", "Position", new Empty());
add("Frame.dblclick.options.position", "Object", "Position", new Empty());
add("Frame.hover.options.position", "Object", "Position", new Empty());
add("ElementHandle.click.options.position", "Object", "Position", new Empty());
add("ElementHandle.dblclick.options.position", "Object", "Position", new Empty());
add("ElementHandle.hover.options.position", "Object", "Position", new Empty());
// The method has custom signatures
add("BrowserContext.cookies", "Promise<Array<Object>>", "Cookie");
add("BrowserContext.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.cookies.expires", "number", "long");
add("BrowserContext.addCookies.cookies", "Array<Object>", "AddCookie");
add("BrowserContext.addCookies.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.addCookies.cookies.expires", "number", "Long", new Empty());
add("BrowserContext.route.url", "string|RegExp|function(URL):boolean", "String");
add("BrowserContext.unroute.url", "string|RegExp|function(URL):boolean", "String");
add("BrowserContext.storageState", "Promise<Object>", "StorageState", new Empty());
add("BrowserContext.waitForEvent.event", "string", "EventType", new Empty());
add("BrowserContext.waitForEvent.optionsOrPredicate", "Function|Object", "String");
add("BrowserContext.waitForEvent", "Promise<Object>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForNavigation.options.url", "string|RegExp|Function", "String");
add("Page.frame.options", "string|Object", "FrameOptions", new Empty());
add("Page.route.url", "string|RegExp|function(URL):boolean", "String");
add("Page.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("Page.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String");
add("Page.waitForEvent.event", "string", "EventType", new Empty());
add("Page.waitForEvent.optionsOrPredicate", "Function|Object", "WaitForEventOptions");
add("Page.waitForEvent", "Promise<Object>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String");
add("Page.waitForResponse.urlOrPredicate", "string|RegExp|function(Response):boolean", "String");
add("Frame.waitForNavigation.options.url", "string|RegExp|Function", "String");
add("Frame.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("Frame.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("ElementHandle.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("ElementHandle.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("FileChooser.setFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("Route.continue.overrides.postData", "string|Buffer", "byte[]");
add("Route.fulfill.response.body", "string|Buffer", "String");
add("BrowserType.launch.options.ignoreDefaultArgs", "boolean|Array<string>", "Boolean");
add("BrowserType.launch.options.firefoxUserPrefs", "Object<string, string|number|boolean>", "String");
add("BrowserType.launch.options.env", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchPersistentContext.options.ignoreDefaultArgs", "boolean|Array<string>", "String");
add("BrowserType.launchPersistentContext.options.env", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchServer.options.ignoreDefaultArgs", "boolean|Array<string>", "String");
add("BrowserType.launchServer.options.firefoxUserPrefs", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchServer.options.env", "Object<string, string|number|boolean>", "String");
add("Logger.log.message", "string|Error", "String");
add("Browser.newContext.options.geolocation.latitude", "number", "double");
add("Browser.newContext.options.geolocation.longitude", "number", "double");
add("Browser.newContext.options.geolocation.accuracy", "number", "double");
add("Browser.newPage.options.geolocation.latitude", "number", "double");
add("Browser.newPage.options.geolocation.longitude", "number", "double");
add("Browser.newPage.options.geolocation.accuracy", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.latitude", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.longitude", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.accuracy", "number", "double");
add("BrowserContext.setGeolocation.geolocation", "null|Object", "Geolocation", new Empty());
add("Browser.newContext.options.geolocation", "Object", "Geolocation", new Empty());
add("Browser.newContext.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty());
add("Browser.newPage.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty());
add("Browser.newPage.options.geolocation", "Object", "Geolocation", new Empty());
add("BrowserType.launchPersistentContext.options.geolocation", "Object", "Geolocation", new Empty());
add("Download.saveAs.path", "string", "Path", new Empty());
add("Download.path", "Promise<null|string>", "Path", new Empty());
add("Download.createReadStream", "Promise<null|Readable>", "InputStream", new Empty());
// Single field options
add("Keyboard.type.options", "Object", "int", new Empty());
add("Keyboard.press.options", "Object", "int", new Empty());
// node.js types
add("BrowserServer.process", "ChildProcess", "Object");
add("Page.pdf", "Promise<Buffer>", "byte[]");
add("Page.screenshot", "Promise<Buffer>", "byte[]");
add("ElementHandle.screenshot", "Promise<Buffer>", "byte[]");
add("Request.postDataBuffer", "null|Buffer", "byte[]");
add("Response.body", "Promise<Buffer>", "byte[]");
add("Response.finished", "Promise<null|Error>", "String");
add("ChromiumBrowser.stopTracing", "Promise<Buffer>", "byte[]");
add("WebSocket.framereceived.payload", "string|Buffer", "byte[]");
add("WebSocket.framesent.payload", "string|Buffer", "byte[]");
// JSON type
add("BrowserContext.addInitScript.arg", "Serializable", "Object");
add("Page.$eval", "Promise<Serializable>", "Object");
add("Page.$$eval", "Promise<Serializable>", "Object");
add("Page.addInitScript.arg", "Serializable", "Object");
add("Page.evaluate", "Promise<Serializable>", "Object");
add("Frame.$eval", "Promise<Serializable>", "Object");
add("Frame.$$eval", "Promise<Serializable>", "Object");
add("Frame.evaluate", "Promise<Serializable>", "Object");
add("ElementHandle.$eval", "Promise<Serializable>", "Object");
add("ElementHandle.$$eval", "Promise<Serializable>", "Object");
add("ElementHandle.evaluate", "Promise<Serializable>", "Object");
add("ElementHandle.jsonValue", "Promise<Serializable>", "Object");
add("JSHandle.evaluate", "Promise<Serializable>", "Object");
add("JSHandle.jsonValue", "Promise<Serializable>", "Object");
add("Response.json", "Promise<Serializable>", "Object");
add("Worker.evaluate", "Promise<Serializable>", "Object");
add("CDPSession.send.params", "Object", "Object", new Empty());
}
Mapping findForPath(String jsonPath) {
return jsonPathToMapping.get(jsonPath);
}
private void add(String jsonPath, String fromType, String toType) {
if (jsonPathToMapping.containsKey(jsonPath)) {
throw new RuntimeException("Duplicate entry: " + jsonPath);
}
jsonPathToMapping.put(jsonPath, new Mapping(fromType, toType));
}
private void add(String jsonPath, String fromType, String toType, CustomMapping factory) {
jsonPathToMapping.put(jsonPath, new Mapping(fromType, toType, factory));
}
private static class PollingOption implements CustomMapping {
@Override
public void defineTypesIn(TypeDefinition scope) {
}
}
private static class Empty implements CustomMapping {
@Override
public void defineTypesIn(TypeDefinition scope) {
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.170.2</version>
<version>0.181.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -34,25 +34,46 @@ public class DriverJar extends Driver {
}
private void installBrowsers() throws IOException, InterruptedException {
Path driver = driverTempDir.resolve("playwright-cli");
String cliFileName = super.cliFileName();
Path driver = driverTempDir.resolve(cliFileName);
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
}
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(10, TimeUnit.MINUTES);
if (!result) {
p.destroy();
throw new RuntimeException("Timed out waiting for browsers to install");
}
}
private static boolean isExecutable(Path filePath) {
String name = filePath.getFileName().toString();
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
}
private void extractDriverToTempDir() throws URISyntaxException, IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI uri = classloader.getResource("driver/" + platformDir()).toURI();
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
Files.list(Paths.get(uri)).forEach(filePath -> {
Path srcRoot = Paths.get(uri);
Files.walk(srcRoot).forEach(fromPath -> {
Path relative = srcRoot.relativize(fromPath);
Path toPath = driverTempDir.resolve(relative.toString());
try {
extractResource(filePath, driverTempDir);
if (Files.isDirectory(fromPath)) {
Files.createDirectories(toPath);
} else {
Files.copy(fromPath, toPath);
if (isExecutable(toPath)) {
toPath.toFile().setExecutable(true, true);
}
}
toPath.toFile().deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri, e);
}
@@ -74,14 +95,6 @@ public class DriverJar extends Driver {
throw new RuntimeException("Unexpected os.name value: " + name);
}
private static Path extractResource(Path from, Path toDir) throws IOException {
Path path = toDir.resolve(from.getFileName().toString());
Files.copy(from, path);
path.toFile().setExecutable(true);
path.toFile().deleteOnExit();
return path;
}
@Override
Path driverDir() {
return driverTempDir;
@@ -23,6 +23,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestInstall {
@@ -30,14 +31,19 @@ public class TestInstall {
void playwrightCliInstalled() throws Exception {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
Path cli = Driver.ensureDriverInstalled();
assertTrue(Files.exists(cli));
try {
Path cli = Driver.ensureDriverInstalled();
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for browsers to install");
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for browsers to install");
} catch (Exception e) {
e.printStackTrace();
assertNull(e);
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.170.2</version>
<version>0.181.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -43,14 +43,18 @@ public abstract class Driver {
try {
instance = createDriver();
} catch (Exception exception) {
throw new RuntimeException("Failed to find playwright-cli", exception);
throw new RuntimeException("Failed to create driver", exception);
}
}
String name = System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright-cli.exe" : "playwright-cli";
String name = instance.cliFileName();
return instance.driverDir().resolve(name);
}
protected String cliFileName() {
return System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright.cmd" : "playwright.sh";
}
private static Driver createDriver() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
+7
View File
@@ -0,0 +1,7 @@
This directory contains sample [`pom.xml`](./pom.xml) and source code for the Playwright examples.
You can run them in terminal like this:
```sh
mvn compile exec:java -Dexec.mainClass=org.example.PageScreenshot
```
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>0.1-SNAPSHOT</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.180.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,40 @@
/*
* 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 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();
}
}
@@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.example;
package org.example;
import com.microsoft.playwright.*;
import java.io.File;
import java.nio.file.Paths;
public class Main {
public class InterceptNetworkRequests {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://webkit.org");
page.click("text=check feature status");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("s.png")));
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
}
@@ -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 org.example;
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();
}
}
@@ -0,0 +1,44 @@
/*
* 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;
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();
}
playwright.close();
}
}
+1 -2
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.170.2</version>
<version>0.181.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -72,7 +72,6 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
@@ -20,24 +20,18 @@ import java.util.*;
/**
* The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by
* <p>
* assistive technology such as screen readers or
* <p>
* switches.
* <p>
* Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
* <p>
* 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>
* Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different
* <p>
* platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
* <p>
* Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by
* <p>
* assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the
* <p>
* "interesting" nodes of the tree.
*
* <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 {
@@ -50,7 +44,7 @@ public interface Accessibility {
*/
public ElementHandle root;
public SnapshotOptions withInterestingOnly(Boolean interestingOnly) {
public SnapshotOptions withInterestingOnly(boolean interestingOnly) {
this.interestingOnly = interestingOnly;
return this;
}
@@ -64,13 +58,10 @@ public interface Accessibility {
}
/**
* Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
* <p>
* page.
* <p>
* <strong>NOTE</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
* <p>
*
* <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}.
* <p>
*/
AccessibilityNode snapshot(SnapshotOptions options);
}
@@ -18,15 +18,12 @@ package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
/**
* A Browser is created when Playwright connects to a browser instance, either through browserType.launch([options]) or
* <p>
* browserType.connect(params).
* <p>
* browserType.connect(params) and browserType.launch([options]) always return a specific browser instance, based on the browser
* <p>
* being connected to or launched.
* - extends: [EventEmitter]
*
* <p> A Browser is created via [{@code method: BrowserType.launch}]. An example of using a {@code Browser} to create a [Page]:
*/
public interface Browser {
class VideoSize {
@@ -47,66 +44,16 @@ public interface Browser {
}
}
enum EventType {
DISCONNECTED,
}
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onDisconnected(Consumer<Browser> handler);
void offDisconnected(Consumer<Browser> handler);
class NewContextOptions {
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;
}
}
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.
* 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;
/**
@@ -145,165 +92,213 @@ public interface Browser {
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.
*/
public Boolean acceptDownloads;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public Boolean bypassCSP;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* 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}'.
*/
public Page.Viewport viewport;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
public ColorScheme colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public Integer deviceScaleFactor;
public Double deviceScaleFactor;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported in Firefox.
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Boolean isMobile;
public Map<String, String> extraHTTPHeaders;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public Boolean hasTouch;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public BrowserContext.HTTPCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public Boolean isMobile;
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public Boolean javaScriptEnabled;
/**
* Changes the timezone of the context. See ICUs {@code metaZones.txt} for a list of supported timezone IDs.
*/
public String timezoneId;
public Geolocation geolocation;
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language} request header value as well as number and date formatting rules.
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public String locale;
/**
* A list of permissions to grant to all pages in this context. See browserContext.grantPermissions(permissions[, options]) for more details.
*/
public List<String> permissions;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public Boolean offline;
/**
* Credentials for HTTP authentication.
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public List<String> permissions;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See page.emulateMedia(params) for more details. Defaults to '{@code light}'.
*/
public ColorScheme colorScheme;
/**
* Logger sink for Playwright logging.
*/
public Logger logger;
/**
* Enables HAR recording for all pages into {@code recordHar.path} file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved.
*/
public RecordHar recordHar;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved.
*/
public RecordVideo recordVideo;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
*/
public Proxy proxy;
/**
* Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState([options]). Either a path to the file with saved storage, or an object with the following fields:
* 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.
*/
public RecordHar recordHar;
/**
* 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.
*/
public RecordVideo recordVideo;
/**
* 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:
*/
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.
*/
public String timezoneId;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public NewContextOptions withAcceptDownloads(Boolean acceptDownloads) {
public NewContextOptions withAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public NewContextOptions withIgnoreHTTPSErrors(Boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewContextOptions withBypassCSP(Boolean bypassCSP) {
public NewContextOptions withBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public NewContextOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
}
public NewContextOptions withUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public NewContextOptions withDeviceScaleFactor(Integer deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewContextOptions withIsMobile(Boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewContextOptions withHasTouch(Boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewContextOptions withJavaScriptEnabled(Boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewContextOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewContextOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewContextOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public NewContextOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public NewContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewContextOptions withOffline(Boolean offline) {
this.offline = offline;
return this;
}
public NewContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
return this;
}
public NewContextOptions withColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public NewContextOptions withLogger(Logger logger) {
this.logger = logger;
public NewContextOptions withDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewContextOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewContextOptions withHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
return this;
}
public NewContextOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewContextOptions withIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewContextOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewContextOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public NewContextOptions withOffline(boolean offline) {
this.offline = offline;
return this;
}
public NewContextOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
@@ -312,10 +307,6 @@ public interface Browser {
this.recordVideo = new RecordVideo();
return this.recordVideo;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public NewContextOptions withStorageState(BrowserContext.StorageState storageState) {
this.storageState = storageState;
this.storageStatePath = null;
@@ -326,61 +317,32 @@ public interface Browser {
this.storageStatePath = storageStatePath;
return this;
}
public NewContextOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewContextOptions withUserAgent(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 withDevice(DeviceDescriptor device) {
withViewport(device.viewport().width(), device.viewport().height());
withUserAgent(device.userAgent());
withDeviceScaleFactor(device.deviceScaleFactor());
withIsMobile(device.isMobile());
withHasTouch(device.hasTouch());
return this;
}
}
class NewPageOptions {
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;
}
}
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.
* 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;
/**
@@ -419,165 +381,213 @@ public interface Browser {
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.
*/
public Boolean acceptDownloads;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public Boolean bypassCSP;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* 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}'.
*/
public Page.Viewport viewport;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
public ColorScheme colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public Integer deviceScaleFactor;
public Double deviceScaleFactor;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported in Firefox.
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Boolean isMobile;
public Map<String, String> extraHTTPHeaders;
public Geolocation geolocation;
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public Boolean hasTouch;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public BrowserContext.HTTPCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public Boolean isMobile;
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public Boolean javaScriptEnabled;
/**
* Changes the timezone of the context. See ICUs {@code metaZones.txt} for a list of supported timezone IDs.
*/
public String timezoneId;
public Geolocation geolocation;
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language} request header value as well as number and date formatting rules.
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public String locale;
/**
* A list of permissions to grant to all pages in this context. See browserContext.grantPermissions(permissions[, options]) for more details.
*/
public List<String> permissions;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public Boolean offline;
/**
* Credentials for HTTP authentication.
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public List<String> permissions;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See page.emulateMedia(params) for more details. Defaults to '{@code light}'.
*/
public ColorScheme colorScheme;
/**
* Logger sink for Playwright logging.
*/
public Logger logger;
/**
* Enables HAR recording for all pages into {@code recordHar.path} file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved.
*/
public RecordHar recordHar;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved.
*/
public RecordVideo recordVideo;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
*/
public Proxy proxy;
/**
* Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState([options]). Either a path to the file with saved storage, or an object with the following fields:
* 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.
*/
public RecordHar recordHar;
/**
* 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.
*/
public RecordVideo recordVideo;
/**
* 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:
*/
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.
*/
public String timezoneId;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public NewPageOptions withAcceptDownloads(Boolean acceptDownloads) {
public NewPageOptions withAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public NewPageOptions withIgnoreHTTPSErrors(Boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewPageOptions withBypassCSP(Boolean bypassCSP) {
public NewPageOptions withBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public NewPageOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
}
public NewPageOptions withUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public NewPageOptions withDeviceScaleFactor(Integer deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewPageOptions withIsMobile(Boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewPageOptions withHasTouch(Boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewPageOptions withJavaScriptEnabled(Boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewPageOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewPageOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewPageOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public NewPageOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public NewPageOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewPageOptions withOffline(Boolean offline) {
this.offline = offline;
return this;
}
public NewPageOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
return this;
}
public NewPageOptions withColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public NewPageOptions withLogger(Logger logger) {
this.logger = logger;
public NewPageOptions withDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public NewPageOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public NewPageOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public NewPageOptions withHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public NewPageOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
return this;
}
public NewPageOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public NewPageOptions withIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public NewPageOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public NewPageOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public NewPageOptions withOffline(boolean offline) {
this.offline = offline;
return this;
}
public NewPageOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
@@ -586,10 +596,6 @@ public interface Browser {
this.recordVideo = new RecordVideo();
return this.recordVideo;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public NewPageOptions withStorageState(BrowserContext.StorageState storageState) {
this.storageState = storageState;
this.storageStatePath = null;
@@ -600,22 +606,39 @@ public interface Browser {
this.storageStatePath = storageStatePath;
return this;
}
public NewPageOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public NewPageOptions withUserAgent(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 withDevice(DeviceDescriptor device) {
withViewport(device.viewport().width(), device.viewport().height());
withUserAgent(device.userAgent());
withDeviceScaleFactor(device.deviceScaleFactor());
withIsMobile(device.isMobile());
withHasTouch(device.hasTouch());
return this;
}
}
/**
* In case this browser is obtained using browserType.launch([options]), closes the browser and all of its pages (if any were
* <p>
* opened).
* <p>
* In case this browser is obtained using browserType.connect(params), clears all created contexts belonging to this browser
* <p>
* and disconnects from the browser server.
* <p>
* The Browser object itself is considered to be disposed and cannot be used anymore.
* In case this browser is obtained using [{@code method: 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.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*/
void close();
/**
* Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
* <p>
*/
List<BrowserContext> contexts();
/**
@@ -627,7 +650,6 @@ public interface Browser {
}
/**
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
* <p>
*/
BrowserContext newContext(NewContextOptions options);
default Page newPage() {
@@ -635,12 +657,10 @@ 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
* <p>
* testing frameworks should explicitly create browser.newContext([options]) followed by the browserContext.newPage() to
* <p>
* control their exact life times.
*
* <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.
*/
Page newPage(NewPageOptions options);
/**
@@ -23,16 +23,15 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
/**
* 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
* <p>
* - extends: [EventEmitter]
*
* <p> 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
* <p>
*
* <p> Playwright allows creation of "incognito" browser contexts with {@code browser.newContext()} method. "Incognito" browser
* contexts don't write any browsing data to disk.
* <p>
*/
public interface BrowserContext {
enum SameSite { STRICT, LAX, NONE }
@@ -95,53 +94,54 @@ public interface BrowserContext {
}
}
class WaitForEventOptions {
public Integer timeout;
public Predicate<Event<EventType>> predicate;
public WaitForEventOptions withTimeout(int millis) {
timeout = millis;
return this;
}
public WaitForEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
void onClose(Consumer<BrowserContext> handler);
void offClose(Consumer<BrowserContext> handler);
void onPage(Consumer<Page> 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); }
enum EventType {
CLOSE,
PAGE,
}
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
class AddCookie {
/**
* **required**
*/
public String name;
/**
* **required**
*/
public String value;
/**
* either url or domain / path are required
* either url or domain / path are required. Optional.
*/
public String url;
/**
* either url or domain / path are required
* either url or domain / path are required Optional.
*/
public String domain;
/**
* either url or domain / path are required
* either url or domain / path are required Optional.
*/
public String path;
/**
* Unix time in seconds.
* Unix time in seconds. Optional.
*/
public Double expires;
/**
* Optional.
*/
public Long expires;
public Boolean httpOnly;
/**
* Optional.
*/
public Boolean secure;
/**
* Optional.
*/
public SameSite sameSite;
public AddCookie withName(String name) {
@@ -164,15 +164,15 @@ public interface BrowserContext {
this.path = path;
return this;
}
public AddCookie withExpires(Long expires) {
public AddCookie withExpires(double expires) {
this.expires = expires;
return this;
}
public AddCookie withHttpOnly(Boolean httpOnly) {
public AddCookie withHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
return this;
}
public AddCookie withSecure(Boolean secure) {
public AddCookie withSecure(boolean secure) {
this.secure = secure;
return this;
}
@@ -189,7 +189,7 @@ public interface BrowserContext {
/**
* Unix time in seconds.
*/
private long expires;
private double expires;
private boolean httpOnly;
private boolean secure;
private SameSite sameSite;
@@ -206,7 +206,7 @@ public interface BrowserContext {
public String path() {
return this.path;
}
public long expires() {
public double expires() {
return this.expires;
}
public boolean httpOnly() {
@@ -221,18 +221,19 @@ public interface BrowserContext {
}
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is supported. When passing by value, multiple arguments are supported.
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
* supported. When passing by value, multiple arguments are supported.
*/
public Boolean handle;
public ExposeBindingOptions withHandle(Boolean handle) {
public ExposeBindingOptions withHandle(boolean handle) {
this.handle = handle;
return this;
}
}
class GrantPermissionsOptions {
/**
* The origin to grant permissions to, e.g. "https://example.com".
* The [origin] to grant permissions to, e.g. "https://example.com".
*/
public String origin;
@@ -243,7 +244,8 @@ public interface BrowserContext {
}
class StorageStateOptions {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public Path path;
@@ -254,9 +256,7 @@ public interface BrowserContext {
}
/**
* Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be
* <p>
* obtained via browserContext.cookies([urls]).
* <p>
* obtained via [{@code method: BrowserContext.cookies}].
*/
void addCookies(List<AddCookie> cookies);
default void addInitScript(String script) {
@@ -264,20 +264,16 @@ public interface BrowserContext {
}
/**
* Adds a script which would be evaluated in one of the following scenarios:
* <p>
* Whenever a page is created in the browser context or is navigated.
* <p>
* 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.
* <p>
* The script is evaluated after the document was created but before any of its scripts were run. This is useful to amend
* <p>
* - 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.
*
* <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>
*
* <p>
* <strong>NOTE</strong> The order of evaluation of multiple scripts installed via browserContext.addInitScript(script[, arg]) and
* <p>
* page.addInitScript(script[, arg]) is not defined.
*
* <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.
*
* @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).
*/
@@ -292,79 +288,78 @@ public interface BrowserContext {
void clearCookies();
/**
* Clears all permission overrides for the browser context.
* <p>
*/
void clearPermissions();
/**
* Closes the browser context. All the pages that belong to the browser context will be closed.
* <p>
* <strong>NOTE</strong> the default browser context cannot be closed.
*
* <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
* <p>
* are returned.
*
* @param urls Optional list of URLs.
*/
List<Cookie> cookies(List<String> urls);
default void exposeBinding(String name, Page.Binding playwrightBinding) {
exposeBinding(name, playwrightBinding, null);
default void exposeBinding(String name, Page.Binding 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
* <p>
* called, the function executes {@code playwrightBinding} in Node.js and returns a Promise which resolves to the return value
* <p>
* of {@code playwrightBinding}. If the {@code playwrightBinding} returns a Promise, it will be awaited.
* <p>
* The first argument of the {@code playwrightBinding} function contains information about the caller: {@code { browserContext: BrowserContext, page: Page, frame: Frame }}.
* <p>
* See page.exposeBinding(name, playwrightBinding[, options]) for page-only version.
* 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.
*
* <p> The first argument of the {@code callback} function contains information about the caller: `{ browserContext: BrowserContext,
* page: Page, frame: Frame }`.
*
* <p> See [{@code method: Page.exposeBinding}] for page-only version.
*
*
* @param name Name of the function on the window object.
* @param playwrightBinding Callback function that will be called in the Playwright's context.
* @param callback Callback function that will be called in the Playwright's context.
*/
void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options);
void exposeBinding(String name, Page.Binding 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
* <p>
* called, the function executes {@code playwrightFunction} in Node.js and returns a Promise which resolves to the return value
* <p>
* of {@code playwrightFunction}.
* <p>
* If the {@code playwrightFunction} returns a Promise, it will be awaited.
* <p>
* See page.exposeFunction(name, playwrightFunction) for page-only version.
* called, the function executes {@code callback} and returns a [Promise] which resolves to the return value of {@code callback}.
*
* <p> If the {@code callback} returns a [Promise], it will be awaited.
*
* <p> See [{@code method: Page.exposeFunction}] for page-only version.
*
*
* @param name Name of the function on the window object.
* @param playwrightFunction Callback function that will be called in the Playwright's context.
* @param callback Callback function that will be called in the Playwright's context.
*/
void exposeFunction(String name, Page.Function playwrightFunction);
void exposeFunction(String name, Page.Function callback);
default void grantPermissions(List<String> permissions) {
grantPermissions(permissions, null);
}
/**
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* <p>
* 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'}
* - {@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'}
*/
void grantPermissions(List<String> permissions, GrantPermissionsOptions options);
/**
@@ -373,83 +368,70 @@ 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
* <p>
* find them using chromiumBrowserContext.backgroundPages().
* find them using [{@code method: ChromiumBrowserContext.backgroundPages}].
*/
List<Page> pages();
void route(String url, Consumer<Route> handler);
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
* <p>
* 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>
* Page routes (set up with page.route(url, handler)) take precedence over browser context routes when request matches both
* <p>
*
* <p> or the same snippet using a regex pattern instead:
*
* <p> Page routes (set up with [{@code method: 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.
*
* <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(Predicate<String> url, Consumer<Route> handler);
/**
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
* <p>
* page.goBack([options])
* <p>
* page.goForward([options])
* <p>
* page.goto(url[, options])
* <p>
* page.reload([options])
* <p>
* page.setContent(html[, options])
* <p>
* page.waitForNavigation([options])
* <p>
*
* <p>
* <strong>NOTE</strong> page.setDefaultNavigationTimeout(timeout) and page.setDefaultTimeout(timeout) take priority over
* <p>
* browserContext.setDefaultNavigationTimeout(timeout).
* - [{@code method: Page.goBack}]
* - [{@code method: Page.goForward}]
* - [{@code method: Page.goto}]
* - [{@code method: Page.reload}]
* - [{@code method: Page.setContent}]
* - [{@code method: Page.waitForNavigation}]
*
* <p> <strong>NOTE:</strong> [{@code method: Page.setDefaultNavigationTimeout}] and [{@code method: Page.setDefaultTimeout}] take priority over
* [{@code method: BrowserContext.setDefaultNavigationTimeout}].
*
* @param timeout Maximum navigation time in milliseconds
*/
void setDefaultNavigationTimeout(int timeout);
void setDefaultNavigationTimeout(double timeout);
/**
* This setting will change the default maximum time for all the methods accepting {@code timeout} option.
* <p>
* <strong>NOTE</strong> page.setDefaultNavigationTimeout(timeout), page.setDefaultTimeout(timeout) and
* <p>
* browserContext.setDefaultNavigationTimeout(timeout) take priority over browserContext.setDefaultTimeout(timeout).
*
* <p> <strong>NOTE:</strong> [{@code method: Page.setDefaultNavigationTimeout}], [{@code method: Page.setDefaultTimeout}] and
* [{@code method: BrowserContext.setDefaultNavigationTimeout}] take priority over [{@code method: BrowserContext.setDefaultTimeout}].
*
* @param timeout Maximum time in milliseconds
*/
void setDefaultTimeout(int timeout);
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
* <p>
* with page-specific extra HTTP headers set with page.setExtraHTTPHeaders(headers). If page overrides a particular header,
* <p>
* page-specific header value will be used instead of the browser context header value.
* <p>
* <strong>NOTE</strong> {@code browserContext.setExtraHTTPHeaders} does not guarantee the order of headers in the outgoing requests.
* 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.
*
* <p> <strong>NOTE:</strong> [{@code method: 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.
* <p>
*
* <p>
* <strong>NOTE</strong> Consider using browserContext.grantPermissions(permissions[, options]) to grant permissions for the browser context pages to
* <p>
*
* <p> <strong>NOTE:</strong> Consider using [{@code method: BrowserContext.grantPermissions}] to grant permissions for the browser context pages to
* read its geolocation.
*/
void setGeolocation(Geolocation geolocation);
/**
*
*
*
* @param offline Whether to emulate network being offline for the browser context.
*/
void setOffline(boolean offline);
@@ -466,29 +448,13 @@ public interface BrowserContext {
void unroute(String url, Consumer<Route> handler);
void unroute(Pattern url, Consumer<Route> handler);
/**
* Removes a route created with browserContext.route(url, handler). When {@code handler} is not specified, removes all routes for the
* <p>
* {@code url}.
* @param url A glob pattern, regex pattern or predicate receiving URL used to register a routing with browserContext.route(url, handler).
* @param handler Optional handler function used to register a routing with browserContext.route(url, handler).
* Removes a route created with [{@code method: 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}].
*/
void unroute(Predicate<String> url, Consumer<Route> handler);
default Deferred<Event<EventType>> waitForEvent(EventType event) {
return waitForEvent(event, (WaitForEventOptions) null);
}
default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {
WaitForEventOptions options = new WaitForEventOptions();
options.predicate = predicate;
return waitForEvent(event, options);
}
/**
* Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy
* <p>
* value. Will throw an error if the context closes before the event is fired. Returns the event data value.
* <p>
*
* @param event Event name, same one would pass into {@code browserContext.on(event)}.
*/
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
}
@@ -21,15 +21,14 @@ import java.util.*;
/**
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
* <p>
* typical example of using Playwright to drive automation:
* <p>
*/
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.
* 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;
/**
@@ -69,37 +68,43 @@ public interface BrowserType {
}
}
/**
* Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to {@code true} unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is resolved relative to current working directory. Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk.
*/
public Path executablePath;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found here.
* 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/).
*/
public List<String> args;
/**
* 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}.
*/
public Boolean ignoreDefaultArgs;
/**
* Network proxy settings.
*/
public Proxy proxy;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is deleted when browser is closed.
*/
public Path downloadsPath;
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public Boolean chromiumSandbox;
/**
* Firefox user preferences. Learn more about the Firefox user preferences at {@code about:config}.
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
*/
public String firefoxUserPrefs;
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed.
*/
public Path downloadsPath;
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
*/
public Map<String, String> env;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
* or WebKit, use at your own risk.
*/
public 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).
*/
public Map<String, Object> firefoxUserPrefs;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
*/
public Boolean handleSIGHUP;
/**
* Close the browser process on Ctrl-C. Defaults to {@code true}.
*/
@@ -109,99 +114,102 @@ public interface BrowserType {
*/
public Boolean handleSIGTERM;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
* 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
* {@code devtools} option is {@code true}.
*/
public Boolean handleSIGHUP;
public Boolean headless;
/**
* Logger sink for Playwright logging.
* 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}.
*/
public Logger logger;
public List<String> ignoreDefaultArgs;
public Boolean ignoreAllDefaultArgs;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Network proxy settings.
*/
public Integer timeout;
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
*/
public String env;
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless} option will be set {@code false}.
*/
public Boolean devtools;
public Proxy proxy;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
*/
public Integer slowMo;
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public Double timeout;
public LaunchOptions withHeadless(Boolean headless) {
this.headless = headless;
public LaunchOptions withArgs(List<String> args) {
this.args = args;
return this;
}
public LaunchOptions withChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchOptions withDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchOptions withDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchOptions withEnv(Map<String, String> env) {
this.env = env;
return this;
}
public LaunchOptions withExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
public LaunchOptions withArgs(List<String> args) {
this.args = args;
public LaunchOptions withFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
public LaunchOptions withIgnoreDefaultArgs(Boolean ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
public LaunchOptions withHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchOptions withHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchOptions withHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchOptions withHeadless(boolean headless) {
this.headless = headless;
return this;
}
public LaunchOptions withIgnoreDefaultArgs(List<String> argumentNames) {
this.ignoreDefaultArgs = argumentNames;
return this;
}
public LaunchOptions withIgnoreAllDefaultArgs(boolean ignore) {
this.ignoreAllDefaultArgs = ignore;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public LaunchOptions withDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchOptions withChromiumSandbox(Boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchOptions withFirefoxUserPrefs(String firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
public LaunchOptions withHandleSIGINT(Boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchOptions withHandleSIGTERM(Boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchOptions withHandleSIGHUP(Boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchOptions withLogger(Logger logger) {
this.logger = logger;
return this;
}
public LaunchOptions withTimeout(Integer timeout) {
this.timeout = timeout;
return this;
}
public LaunchOptions withEnv(String env) {
this.env = env;
return this;
}
public LaunchOptions withDevtools(Boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchOptions withSlowMo(Integer slowMo) {
public LaunchOptions withSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public LaunchOptions withTimeout(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.
* 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;
/**
@@ -256,7 +264,7 @@ public interface BrowserType {
return LaunchPersistentContextOptions.this;
}
public RecordHar withOmitContent(Boolean omitContent) {
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
@@ -296,7 +304,9 @@ public interface BrowserType {
*/
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.
* 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;
@@ -316,33 +326,60 @@ public interface BrowserType {
}
}
/**
* Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to {@code true} unless the {@code devtools} option is {@code true}.
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean headless;
public Boolean acceptDownloads;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is resolved relative to current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk.
*/
public Path executablePath;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found here.
* 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/).
*/
public List<String> args;
/**
* 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}.
* Toggles bypassing page's Content-Security-Policy.
*/
public String ignoreDefaultArgs;
/**
* Network proxy settings.
*/
public Proxy proxy;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is deleted when browser is closed.
*/
public Path downloadsPath;
public Boolean bypassCSP;
/**
* Enable Chromium sandboxing. Defaults to {@code true}.
*/
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}'.
*/
public ColorScheme colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public Double deviceScaleFactor;
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
*/
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed.
*/
public Path downloadsPath;
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
*/
public Map<String, String> env;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled
* Chromium, Firefox or WebKit, use at your own risk.
*/
public Path executablePath;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Map<String, String> extraHTTPHeaders;
public Geolocation geolocation;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
*/
public Boolean handleSIGHUP;
/**
* Close the browser process on Ctrl-C. Defaults to {@code true}.
*/
@@ -352,231 +389,200 @@ public interface BrowserType {
*/
public Boolean handleSIGTERM;
/**
* Close the browser process on SIGHUP. Defaults to {@code true}.
* Specifies if viewport supports touch events. Defaults to false.
*/
public Boolean handleSIGHUP;
public Boolean hasTouch;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* 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
* {@code devtools} option is {@code true}.
*/
public Integer timeout;
public Boolean headless;
/**
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
public String env;
public BrowserContext.HTTPCredentials httpCredentials;
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless} option will be set {@code false}.
* 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}.
*/
public Boolean devtools;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. Defaults to 0.
*/
public Integer slowMo;
/**
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
*/
public Boolean acceptDownloads;
public List<String> ignoreDefaultArgs;
public Boolean ignoreAllDefaultArgs;
/**
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
public Boolean bypassCSP;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
*/
public Integer deviceScaleFactor;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported in Firefox.
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
*/
public Boolean isMobile;
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
public Boolean hasTouch;
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
*/
public Boolean javaScriptEnabled;
/**
* Changes the timezone of the context. See ICUs {@code metaZones.txt} for a list of supported timezone IDs.
*/
public String timezoneId;
public Geolocation geolocation;
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language} request header value as well as number and date formatting rules.
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
*/
public String locale;
/**
* A list of permissions to grant to all pages in this context. See browserContext.grantPermissions(permissions[, options]) for more details.
*/
public List<String> permissions;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Whether to emulate network being offline. Defaults to {@code false}.
*/
public Boolean offline;
/**
* Credentials for HTTP authentication.
* A list of permissions to grant to all pages in this context. See [{@code method: BrowserContext.grantPermissions}] for more
* details.
*/
public BrowserContext.HTTPCredentials httpCredentials;
public List<String> permissions;
/**
* Emulates {@code 'prefers-colors-scheme'} media feature, supported values are {@code 'light'}, {@code 'dark'}, {@code 'no-preference'}. See page.emulateMedia(params) for more details. Defaults to '{@code light}'.
* Network proxy settings.
*/
public ColorScheme colorScheme;
public Proxy proxy;
/**
* Logger sink for Playwright logging.
*/
public Logger logger;
/**
* Enables HAR recording for all pages into {@code recordHar.path} file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved.
* 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.
*/
public RecordHar recordHar;
/**
* Enables video recording for all pages into {@code recordVideo.dir} directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved.
* 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.
*/
public RecordVideo recordVideo;
/**
* 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 browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
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.
*/
public String timezoneId;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Page.Viewport viewport;
public LaunchPersistentContextOptions withHeadless(Boolean headless) {
this.headless = headless;
return this;
}
public LaunchPersistentContextOptions withExecutablePath(Path executablePath) {
this.executablePath = executablePath;
public LaunchPersistentContextOptions withAcceptDownloads(boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public LaunchPersistentContextOptions withArgs(List<String> args) {
this.args = args;
return this;
}
public LaunchPersistentContextOptions withIgnoreDefaultArgs(String ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public LaunchPersistentContextOptions withDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchPersistentContextOptions withChromiumSandbox(Boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchPersistentContextOptions withHandleSIGINT(Boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchPersistentContextOptions withHandleSIGTERM(Boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchPersistentContextOptions withHandleSIGHUP(Boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchPersistentContextOptions withTimeout(Integer timeout) {
this.timeout = timeout;
return this;
}
public LaunchPersistentContextOptions withEnv(String env) {
this.env = env;
return this;
}
public LaunchPersistentContextOptions withDevtools(Boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchPersistentContextOptions withSlowMo(Integer slowMo) {
this.slowMo = slowMo;
return this;
}
public LaunchPersistentContextOptions withAcceptDownloads(Boolean acceptDownloads) {
this.acceptDownloads = acceptDownloads;
return this;
}
public LaunchPersistentContextOptions withIgnoreHTTPSErrors(Boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public LaunchPersistentContextOptions withBypassCSP(Boolean bypassCSP) {
public LaunchPersistentContextOptions withBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
return this;
}
public LaunchPersistentContextOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
}
public LaunchPersistentContextOptions withUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public LaunchPersistentContextOptions withDeviceScaleFactor(Integer deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public LaunchPersistentContextOptions withIsMobile(Boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public LaunchPersistentContextOptions withHasTouch(Boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public LaunchPersistentContextOptions withJavaScriptEnabled(Boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public LaunchPersistentContextOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public LaunchPersistentContextOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public LaunchPersistentContextOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public LaunchPersistentContextOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public LaunchPersistentContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public LaunchPersistentContextOptions withOffline(Boolean offline) {
this.offline = offline;
return this;
}
public LaunchPersistentContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
public LaunchPersistentContextOptions withChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
}
public LaunchPersistentContextOptions withColorScheme(ColorScheme colorScheme) {
this.colorScheme = colorScheme;
return this;
}
public LaunchPersistentContextOptions withLogger(Logger logger) {
this.logger = logger;
public LaunchPersistentContextOptions withDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
public LaunchPersistentContextOptions withDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
public LaunchPersistentContextOptions withDownloadsPath(Path downloadsPath) {
this.downloadsPath = downloadsPath;
return this;
}
public LaunchPersistentContextOptions withEnv(Map<String, String> env) {
this.env = env;
return this;
}
public LaunchPersistentContextOptions withExecutablePath(Path executablePath) {
this.executablePath = executablePath;
return this;
}
public LaunchPersistentContextOptions withExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
public LaunchPersistentContextOptions withGeolocation(Geolocation geolocation) {
this.geolocation = geolocation;
return this;
}
public LaunchPersistentContextOptions withHandleSIGHUP(boolean handleSIGHUP) {
this.handleSIGHUP = handleSIGHUP;
return this;
}
public LaunchPersistentContextOptions withHandleSIGINT(boolean handleSIGINT) {
this.handleSIGINT = handleSIGINT;
return this;
}
public LaunchPersistentContextOptions withHandleSIGTERM(boolean handleSIGTERM) {
this.handleSIGTERM = handleSIGTERM;
return this;
}
public LaunchPersistentContextOptions withHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
return this;
}
public LaunchPersistentContextOptions withHeadless(boolean headless) {
this.headless = headless;
return this;
}
public LaunchPersistentContextOptions withHttpCredentials(String username, String password) {
this.httpCredentials = new BrowserContext.HTTPCredentials(username, password);
return this;
}
public LaunchPersistentContextOptions withIgnoreDefaultArgs(List<String> argumentNames) {
this.ignoreDefaultArgs = argumentNames;
return this;
}
public LaunchPersistentContextOptions withIgnoreAllDefaultArgs(boolean ignore) {
this.ignoreAllDefaultArgs = ignore;
return this;
}
public LaunchPersistentContextOptions withIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
public LaunchPersistentContextOptions withIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
public LaunchPersistentContextOptions withJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
public LaunchPersistentContextOptions withLocale(String locale) {
this.locale = locale;
return this;
}
public LaunchPersistentContextOptions withOffline(boolean offline) {
this.offline = offline;
return this;
}
public LaunchPersistentContextOptions withPermissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Proxy setProxy() {
this.proxy = new Proxy();
return this.proxy;
}
public RecordHar setRecordHar() {
this.recordHar = new RecordHar();
return this.recordHar;
@@ -585,6 +591,26 @@ public interface BrowserType {
this.recordVideo = new RecordVideo();
return this.recordVideo;
}
public LaunchPersistentContextOptions withSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public LaunchPersistentContextOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public LaunchPersistentContextOptions withTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
return this;
}
public LaunchPersistentContextOptions withUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public LaunchPersistentContextOptions withViewport(int width, int height) {
this.viewport = new Page.Viewport(width, height);
return this;
}
}
/**
* A path where Playwright expects to find a bundled browser executable.
@@ -595,30 +621,23 @@ public interface BrowserType {
}
/**
* Returns the browser instance.
* <p>
* You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
* <p>
*
* <p>
* **Chromium-only** Playwright can also be used to control the Chrome browser, but it works best with the version of
* <p>
*
* <p> You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
*
* <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
* <p>
* extreme caution.
* <p>
* If Google Chrome (rather than Chromium) is preferred, a Chrome
* <p>
* Canary or Dev
* <p>
* Channel build is suggested.
* <p>
* In browserType.launch([options]) above, any mention of Chromium also applies to Chrome.
* <p>
* See {@code this article} for
* <p>
* a description of the differences between Chromium and Chrome. {@code This article} describes
* <p>
* some differences for Linux users.
* >
* 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)
* describes some differences for Linux users.
*/
Browser launch(LaunchOptions options);
default BrowserContext launchPersistentContext(Path userDataDir) {
@@ -626,11 +645,13 @@ public interface BrowserType {
}
/**
* Returns the persistent browser context instance.
* <p>
* Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* <p>
*
* <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 and Firefox.
*
* @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).
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
/**
@@ -19,12 +19,12 @@ package com.microsoft.playwright;
import java.util.*;
/**
* ConsoleMessage objects are dispatched by page via the page.on('console') event.
* {@code ConsoleMessage} objects are dispatched by page via the [{@code event: Page.console}] event.
*/
public interface ConsoleMessage {
class Location {
/**
* URL of the resource if available, otherwise empty string.
* URL of the resource.
*/
private String url;
/**
@@ -51,9 +51,7 @@ public interface ConsoleMessage {
String text();
/**
* One of the following values: {@code 'log'}, {@code 'debug'}, {@code 'info'}, {@code 'error'}, {@code 'warning'}, {@code 'dir'}, {@code 'dirxml'}, {@code 'table'},
* <p>
* {@code 'trace'}, {@code 'clear'}, {@code 'startGroup'}, {@code 'startGroupCollapsed'}, {@code 'endGroup'}, {@code 'assert'}, {@code 'profile'}, {@code 'profileEnd'},
* <p>
* {@code 'count'}, {@code 'timeEnd'}.
*/
String type();
@@ -23,7 +23,7 @@ public interface DeviceDescriptor {
}
Viewport viewport();
String userAgent();
int deviceScaleFactor();
double deviceScaleFactor();
boolean isMobile();
boolean hasTouch();
BrowserType defaultBrowserType();
@@ -19,7 +19,7 @@ package com.microsoft.playwright;
import java.util.*;
/**
* Dialog objects are dispatched by page via the page.on('dialog') event.
* {@code Dialog} objects are dispatched by page via the [{@code event: Page.dialog}] event.
*/
public interface Dialog {
enum Type { ALERT, BEFOREUNLOAD, CONFIRM, PROMPT }
@@ -29,6 +29,7 @@ public interface Dialog {
}
/**
* Returns when the dialog has been accepted.
*
* @param promptText A text to enter in prompt. Does not cause any effects if the dialog's {@code type} is not prompt. Optional.
*/
void accept(String promptText);
@@ -21,21 +21,16 @@ import java.nio.file.Path;
import java.util.*;
/**
* Download objects are dispatched by page via the page.on('download') event.
* <p>
* All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
* <p>
* {@code Download} objects are dispatched by page via the [{@code event: Page.download}] 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:
* <p>
*
* <p>
* <strong>NOTE</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the
* <p>
* downloaded content. If {@code acceptDownloads} is not set or set to {@code false}, download events are emitted, but the actual
* <p>
* download is not performed and user has no access to the downloaded files.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
*
* <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,16 +51,14 @@ public interface Download {
Path path();
/**
* Saves the download to a user-specified path.
*
* @param path Path where the download should be saved.
*/
void saveAs(Path path);
/**
* Returns suggested filename for this download. It is typically computed by the browser from the
* <p>
* {@code Content-Disposition} response header
* <p>
* or the {@code download} attribute. See the spec on whatwg. Different
* <p>
* [{@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.
*/
String suggestedFilename();
File diff suppressed because it is too large Load Diff
@@ -20,8 +20,7 @@ import java.nio.file.Path;
import java.util.*;
/**
* FileChooser objects are dispatched by the page in the page.on('filechooser') event.
* <p>
* {@code FileChooser} objects are dispatched by the page in the [{@code event: Page.filechooser}] event.
*/
public interface FileChooser {
class FilePayload {
@@ -38,19 +37,22 @@ public interface FileChooser {
class SetFilesOptions {
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to inaccessible pages. Defaults to {@code false}.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
* 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.
*/
public Integer timeout;
public Double timeout;
public SetFilesOptions withNoWaitAfter(Boolean noWaitAfter) {
public SetFilesOptions withNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
public SetFilesOptions withTimeout(Integer timeout) {
public SetFilesOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
@@ -76,10 +78,7 @@ public interface FileChooser {
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
* <p>
* they are resolved relative to the current working directory.
* <p>
* For empty array, clears the selected files.
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
*/
void setFiles(FileChooser.FilePayload[] files, SetFilesOptions options);
}
File diff suppressed because it is too large Load Diff
@@ -19,21 +19,19 @@ package com.microsoft.playwright;
import java.util.*;
/**
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the page.evaluateHandle(pageFunction[, arg]) method.
* <p>
* JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with
* <p>
* jsHandle.dispose(). JSHandles are auto-disposed when their origin frame gets navigated or the parent context gets
* <p>
* destroyed.
* <p>
* JSHandle instances can be used as an argument in page.$eval(selector, pageFunction[, arg]), page.evaluate(pageFunction[, arg]) and page.evaluateHandle(pageFunction[, arg])
* <p>
* methods.
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the [{@code method: Page.evaluateHandle}]
* method.
*
* <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 instances can be used as an argument in [{@code method: Page.$eval}], [{@code method: Page.evaluate}] and
* [{@code method: Page.evaluateHandle}] methods.
*/
public interface JSHandle {
/**
* Returns either {@code null} or the object handle itself, if the object handle is an instance of ElementHandle.
* Returns either {@code null} or the object handle itself, if the object handle is an instance of {@code ElementHandle}.
*/
ElementHandle asElement();
/**
@@ -45,16 +43,13 @@ public interface JSHandle {
}
/**
* Returns the return value of {@code pageFunction}
* <p>
* This method passes this handle as the first argument to {@code pageFunction}.
* <p>
* If {@code pageFunction} returns a Promise, then {@code handle.evaluate} would wait for the promise to resolve and return its
* <p>
*
* <p> This method passes this handle as the first argument to {@code pageFunction}.
*
* <p> If {@code pageFunction} returns a [Promise], then {@code handle.evaluate} would wait for the promise to resolve and return its
* value.
* <p>
* Examples:
* <p>
*
*
*
* @param pageFunction Function to be evaluated in browser context
* @param arg Optional argument to pass to {@code pageFunction}
*/
@@ -64,41 +59,35 @@ public interface JSHandle {
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* <p>
* This method passes this handle as the first argument to {@code pageFunction}.
* <p>
* The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* <p>
*
* <p> This method passes this handle as the first argument to {@code pageFunction}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* in-page object (JSHandle).
* <p>
* If the function passed to the {@code jsHandle.evaluateHandle} returns a Promise, then {@code jsHandle.evaluateHandle} would wait
* <p>
*
* <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>
* See page.evaluateHandle(pageFunction[, arg]) for more details.
*
* <p> See [{@code method: Page.evaluateHandle}] for more details.
*
* @param pageFunction Function to be evaluated
* @param arg Optional argument to pass to {@code pageFunction}
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
/**
* The method returns a map with **own property names** as keys and JSHandle instances for the property values.
* <p>
*/
Map<String, JSHandle> getProperties();
/**
* Fetches a single property from the referenced object.
*
* @param propertyName property to get
*/
JSHandle getProperty(String propertyName);
/**
* Returns a JSON representation of the object. If the object has a
* <p>
* {@code toJSON}
* <p>
* 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
* <p>
* 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.
*/
Object jsonValue();
@@ -19,110 +19,118 @@ package com.microsoft.playwright;
import java.util.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is keyboard.type(text[, options]), which takes raw
* <p>
* characters and generates proper keydown, keypress/input, and keyup events on your page.
* <p>
* For finer control, you can use keyboard.down(key), keyboard.up(key), and keyboard.insertText(text) to manually fire
* <p>
* events as if they were generated from a real keyboard.
* <p>
* An example to trigger select-all with the keyboard
* <p>
* 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.
*
* <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> An example to trigger select-all with the keyboard
*/
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) {
this.delay = delay;
return this;
}
}
class TypeOptions {
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public Double delay;
public TypeOptions withDelay(double delay) {
this.delay = delay;
return this;
}
}
/**
* Dispatches a {@code keydown} event.
* <p>
* {@code key} can specify the intended keyboardEvent.key
* <p>
*
* <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
* <p>
* here. 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},
* <p>
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). 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 suported: {@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
* <p>
*
* <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>
* 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
* <p>
* active. To release the modifier key, use keyboard.up(key).
* <p>
* After the key is pressed once, subsequent calls to keyboard.down(key) will have
* <p>
* repeat set to true. To release the key, use
* <p>
* keyboard.up(key).
* <p>
* <strong>NOTE</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
* <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}].
*
* <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> <strong>NOTE:</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
void down(String key);
/**
* Dispatches only {@code input} event, does not emit the {@code keydown}, {@code keyup} or {@code keypress} events.
* <p>
*
* <p>
* <strong>NOTE</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper case.
*
* <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, 0);
press(key, null);
}
/**
* {@code key} can specify the intended keyboardEvent.key
* <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
* <p>
* here. 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},
* <p>
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). 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 suported: {@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
* <p>
*
* <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
* <p>
*
* <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.
* <p>
* Shortcut for keyboard.down(key) and keyboard.up(key).
*
* <p> Shortcut for [{@code method: Keyboard.down}] and [{@code method: 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, int delay);
void press(String key, PressOptions delay);
default void type(String text) {
type(text, 0);
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 keyboard.press(key[, options]).
* <p>
*
* <p>
* <strong>NOTE</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use [{@code method: Keyboard.press}].
*
* <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, int delay);
void type(String text, TypeOptions delay);
/**
* Dispatches a {@code keyup} event.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
void up(String key);
@@ -1,52 +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.*;
/**
* Playwright generates a lot of logs and they are accessible via the pluggable logger sink.
* <p>
*/
public interface Logger {
enum Severity { ERROR, INFO, VERBOSE, WARNING }
class LogHints {
/**
* preferred logger color
*/
public String color;
public LogHints withColor(String color) {
this.color = color;
return this;
}
}
/**
* Determines whether sink is interested in the logger with the given name and severity.
* @param name logger name
*/
boolean isEnabled(String name, Severity severity);
/**
*
* @param name logger name
* @param message log message format
* @param args message arguments
* @param hints optional formatting hints
*/
void log(String name, Severity severity, String message, List<Object> args, LogHints hints);
}
@@ -20,9 +20,8 @@ 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 page.mouse.
* <p>
*
* <p> Every {@code page} object has its own Mouse, accessible with [{@code property: Page.mouse}].
*/
public interface Mouse {
enum Button { LEFT, MIDDLE, RIGHT }
@@ -33,23 +32,23 @@ public interface Mouse {
*/
public Button button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Integer delay;
public Double delay;
public ClickOptions withButton(Button button) {
this.button = button;
return this;
}
public ClickOptions withClickCount(Integer clickCount) {
public ClickOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
public ClickOptions withDelay(Integer delay) {
public ClickOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -62,13 +61,13 @@ public interface Mouse {
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Integer delay;
public Double delay;
public DblclickOptions withButton(Button button) {
this.button = button;
return this;
}
public DblclickOptions withDelay(Integer delay) {
public DblclickOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -79,7 +78,7 @@ public interface Mouse {
*/
public Button button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
@@ -87,7 +86,7 @@ public interface Mouse {
this.button = button;
return this;
}
public DownOptions withClickCount(Integer clickCount) {
public DownOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
@@ -98,7 +97,7 @@ public interface Mouse {
*/
public Integer steps;
public MoveOptions withSteps(Integer steps) {
public MoveOptions withSteps(int steps) {
this.steps = steps;
return this;
}
@@ -109,7 +108,7 @@ public interface Mouse {
*/
public Button button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
@@ -117,25 +116,26 @@ public interface Mouse {
this.button = button;
return this;
}
public UpOptions withClickCount(Integer clickCount) {
public UpOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
}
default void click(int x, int y) {
default void click(double x, double y) {
click(x, y, null);
}
/**
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]).
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}].
*/
void click(int x, int y, ClickOptions options);
default void dblclick(int x, int y) {
void click(double x, double y, ClickOptions options);
default void dblclick(double x, double y) {
dblclick(x, y, null);
}
/**
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]), mouse.down([options]) and mouse.up([options]).
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}], [{@code method: Mouse.down}] and
* [{@code method: Mouse.up}].
*/
void dblclick(int x, int y, DblclickOptions options);
void dblclick(double x, double y, DblclickOptions options);
default void down() {
down(null);
}
@@ -143,13 +143,13 @@ public interface Mouse {
* Dispatches a {@code mousedown} event.
*/
void down(DownOptions options);
default void move(int x, int y) {
default void move(double x, double y) {
move(x, y, null);
}
/**
* Dispatches a {@code mousemove} event.
*/
void move(int x, int y, MoveOptions options);
void move(double x, double y, MoveOptions options);
default void up() {
up(null);
}
File diff suppressed because it is too large Load Diff
@@ -17,22 +17,39 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.PlaywrightImpl;
import java.util.*;
import java.util.Map;
/**
* Playwright module provides a method to launch a browser instance. The following is a typical example of using Playwright
* to drive automation:
*/
public interface Playwright {
/**
* 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 selectors();
/**
* This object can be used to launch or connect to WebKit, returning instances of {@code WebKitBrowser}.
*/
BrowserType webkit();
public interface Playwright extends AutoCloseable {
static Playwright create() {
return PlaywrightImpl.create();
}
BrowserType chromium();
BrowserType firefox();
BrowserType webkit();
Map<String, DeviceDescriptor> devices();
Selectors selectors();
@Override
void close() throws Exception;
}
@@ -19,24 +19,18 @@ package com.microsoft.playwright;
import java.util.*;
/**
* Whenever the page sends a request for a network resource the following sequence of events are emitted by Page:
* <p>
* page.on('request') emitted when the request is issued by the page.
* <p>
* page.on('response') emitted when/if the response status and headers are received for the request.
* <p>
* page.on('requestfinished') emitted when the response body is downloaded and the request is complete.
* <p>
* If request fails at some point, then instead of {@code 'requestfinished'} event (and possibly instead of 'response' event),
* <p>
* the page.on('requestfailed') event is emitted.
* <p>
* <strong>NOTE</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request
* <p>
* 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
* <p>
* 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.
*
* <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> <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 {
@@ -57,77 +51,84 @@ public interface Request {
/**
* Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC
*/
private int startTime;
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.
* 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 int domainLookupStart;
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.
* 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 int domainLookupEnd;
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.
* 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 int connectStart;
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.
* 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 int secureConnectionStart;
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.
* 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 int connectEnd;
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.
* 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 int requestStart;
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.
* 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 int responseStart;
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.
* 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 int responseEnd;
private double responseEnd;
public int startTime() {
public double startTime() {
return this.startTime;
}
public int domainLookupStart() {
public double domainLookupStart() {
return this.domainLookupStart;
}
public int domainLookupEnd() {
public double domainLookupEnd() {
return this.domainLookupEnd;
}
public int connectStart() {
public double connectStart() {
return this.connectStart;
}
public int secureConnectionStart() {
public double secureConnectionStart() {
return this.secureConnectionStart;
}
public int connectEnd() {
public double connectEnd() {
return this.connectEnd;
}
public int requestStart() {
public double requestStart() {
return this.requestStart;
}
public int responseStart() {
public double responseStart() {
return this.responseStart;
}
public int responseEnd() {
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:
* <p>
*
* <p> Example of logging of all the failed requests:
*/
RequestFailure failure();
/**
* Returns the Frame that initiated this request.
* Returns the {@code Frame} that initiated this request.
*/
Frame frame();
/**
@@ -152,45 +153,36 @@ public interface Request {
byte[] postDataBuffer();
/**
* Request that was redirected by the server to this one, if any.
* <p>
* When the server responds with a redirect, Playwright creates a new Request object. The two requests are connected by
* <p>
*
* <p> When the server responds with a redirect, Playwright creates a new {@code Request} object. The two requests are connected by
* {@code redirectedFrom()} and {@code redirectedTo()} methods. When multiple server redirects has happened, it is possible to
* <p>
* 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}:
* <p>
* If the website {@code https://google.com} has no redirects:
* <p>
*
* <p> For example, if the website {@code http://example.com} redirects to {@code https://example.com}:
*
* <p> If the website {@code https://google.com} has no redirects:
*/
Request redirectedFrom();
/**
* New request issued by the browser if the server responded with redirect.
* <p>
* This method is the opposite of request.redirectedFrom():
* <p>
*
* <p> This method is the opposite of [{@code method: Request.redirectedFrom}]:
*/
Request redirectedTo();
/**
* Contains the request's resource type as it was perceived by the rendering engine. ResourceType will be one of the
* <p>
* following: {@code document}, {@code stylesheet}, {@code image}, {@code media}, {@code font}, {@code script}, {@code texttrack}, {@code xhr}, {@code fetch}, {@code eventsource},
* <p>
* {@code websocket}, {@code manifest}, {@code other}.
*/
String resourceType();
/**
* Returns the matching Response object, or {@code null} if the response was not received due to error.
* Returns the matching {@code Response} object, or {@code null} if the response was not received due to error.
*/
Response response();
/**
* Returns resource timing information for given request. Most of the timing values become available upon the response,
* <p>
* {@code responseEnd} becomes available when request finishes. Find more information at Resource Timing
* <p>
* API.
* <p>
* {@code responseEnd} becomes available when request finishes. Find more information at
* [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming).
*/
RequestTiming timing();
/**
@@ -19,7 +19,7 @@ package com.microsoft.playwright;
import java.util.*;
/**
* Response class represents responses which are received by page.
* {@code Response} class represents responses which are received by page.
*/
public interface Response {
/**
@@ -31,7 +31,7 @@ public interface Response {
*/
String finished();
/**
* Returns the Frame that initiated this response.
* Returns the {@code Frame} that initiated this response.
*/
Frame frame();
/**
@@ -43,7 +43,7 @@ public interface Response {
*/
boolean ok();
/**
* Returns the matching Request object.
* Returns the matching {@code Request} object.
*/
Request request();
/**
@@ -21,16 +21,15 @@ import java.nio.file.Path;
import java.util.*;
/**
* Whenever a network route is set up with page.route(url, handler) or browserContext.route(url, handler), the {@code Route} object allows to
* <p>
* handle the route.
* 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.
*/
public interface Route {
class ContinueOverrides {
class ContinueOptions {
/**
* If set changes the request URL. New URL must have same protocol as original one.
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public String url;
public Map<String, String> headers;
/**
* If set changes the request method (e.g. GET or POST)
*/
@@ -40,99 +39,103 @@ public interface Route {
*/
public byte[] postData;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
* If set changes the request URL. New URL must have same protocol as original one.
*/
public Map<String, String> headers;
public String url;
public ContinueOverrides withUrl(String url) {
this.url = url;
return this;
}
public ContinueOverrides withMethod(String method) {
this.method = method;
return this;
}
public ContinueOverrides withPostData(String postData) {
this.postData = postData.getBytes(StandardCharsets.UTF_8);
return this;
}
public ContinueOverrides withPostData(byte[] postData) {
this.postData = postData;
return this;
}
public ContinueOverrides withHeaders(Map<String, String> headers) {
public ContinueOptions withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public ContinueOptions withMethod(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) {
this.postData = postData;
return this;
}
public ContinueOptions withUrl(String url) {
this.url = url;
return this;
}
}
class FulfillResponse {
class FulfillOptions {
/**
* Response status code, defaults to {@code 200}.
* Response body.
*/
public int status;
/**
* Optional response headers. Header values will be converted to a string.
*/
public Map<String, String> headers;
public String body;
public byte[] bodyBytes;
/**
* If set, equals to setting {@code Content-Type} response header.
*/
public String contentType;
/**
* Optional response body.
* Response headers. Header values will be converted to a string.
*/
public String body;
public byte[] bodyBytes;
public Map<String, String> headers;
/**
* Optional file path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it is resolved relative to current working directory.
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
* is resolved relative to the current working directory.
*/
public Path path;
/**
* Response status code, defaults to {@code 200}.
*/
public Integer status;
public FulfillResponse withStatus(int status) {
this.status = status;
return this;
}
public FulfillResponse withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public FulfillResponse withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public FulfillResponse withBody(byte[] body) {
public FulfillOptions withBody(byte[] body) {
this.bodyBytes = body;
return this;
}
public FulfillResponse withBody(String body) {
public FulfillOptions withBody(String body) {
this.body = body;
return this;
}
public FulfillResponse withPath(Path path) {
public FulfillOptions withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public FulfillOptions withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public FulfillOptions withPath(Path path) {
this.path = path;
return this;
}
public FulfillOptions withStatus(int status) {
this.status = status;
return this;
}
}
default void abort() {
abort(null);
}
/**
* 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.
* - {@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.
*/
void abort(String errorCode);
default void continue_() {
@@ -140,16 +143,15 @@ public interface Route {
}
/**
* Continues route's request with optional overrides.
* <p>
*
* @param overrides Optional request overrides, can override following properties:
*/
void continue_(ContinueOverrides overrides);
void continue_(ContinueOptions options);
default void fulfill() {
fulfill(null);
}
/**
* Fulfills route's request with given response.
* @param response Response that will fulfill this route's request.
*/
void fulfill(FulfillResponse response);
void fulfill(FulfillOptions options);
/**
* A request to be routed.
*/
@@ -20,18 +20,19 @@ import java.nio.file.Path;
import java.util.*;
/**
* Selectors can be used to install custom selector engines. See Working with selectors for more
* <p>
* information.
* Selectors can be used to install custom selector engines. See
* [Working with selectors](./selectors.md#working-with-selectors) for more information.
*/
public interface Selectors {
class RegisterOptions {
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not guaranteed when this engine is used together with other registered engines.
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
* guaranteed when this engine is used together with other registered engines.
*/
public Boolean contentScript;
public RegisterOptions withContentScript(Boolean contentScript) {
public RegisterOptions withContentScript(boolean contentScript) {
this.contentScript = contentScript;
return this;
}
@@ -41,9 +42,10 @@ public interface Selectors {
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:
* <p>
*
* @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 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);
@@ -19,9 +19,10 @@ package com.microsoft.playwright;
import java.util.*;
/**
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. page.waitForSelector(selector[, options]) or
* <p>
* browserType.launch([options]).
* - 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}].
*/
public interface TimeoutError {
}
@@ -20,13 +20,12 @@ import java.util.*;
/**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
* <p>
* touchscreen can only be used in browser contexts that have been intialized with {@code hasTouch} set to true.
*/
public interface Touchscreen {
/**
* Dispatches a {@code touchstart} and {@code touchend} event with a single touch at the position ({@code x},{@code y}).
*/
void tap(int x, int y);
void tap(double x, double y);
}
@@ -16,18 +16,17 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* When browser context is created with the {@code videosPath} option, each page has a video object associated with it.
* <p>
*/
public interface Video {
/**
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
* <p>
* upon closing the browser context.
*/
String path();
Path path();
}
@@ -17,10 +17,11 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The WebSocket class represents websocket connections in the page.
* The {@code WebSocket} class represents websocket connections in the page.
*/
public interface WebSocket {
interface FrameData {
@@ -28,28 +29,50 @@ public interface WebSocket {
String text();
}
class WaitForEventOptions {
public Integer timeout;
public Predicate<Event<EventType>> predicate;
public WaitForEventOptions withTimeout(int millis) {
timeout = millis;
return this;
}
public WaitForEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
void onClose(Consumer<WebSocket> handler);
void offClose(Consumer<WebSocket> handler);
void onFrameReceived(Consumer<FrameData> handler);
void offFrameReceived(Consumer<FrameData> handler);
void onFrameSent(Consumer<FrameData> handler);
void offFrameSent(Consumer<FrameData> handler);
void onSocketError(Consumer<String> handler);
void offSocketError(Consumer<String> handler);
class WaitForFrameReceivedOptions {
public Double timeout;
public WaitForFrameReceivedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options);
default FrameData waitForFrameReceived(Runnable code) { return waitForFrameReceived(code, null); }
enum EventType {
CLOSE,
FRAMERECEIVED,
FRAMESENT,
SOCKETERROR,
class WaitForFrameSentOptions {
public Double timeout;
public WaitForFrameSentOptions withTimeout(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); }
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
/**
* Indicates that the web socket has been closed.
*/
@@ -58,22 +81,5 @@ public interface WebSocket {
* Contains the URL of the WebSocket.
*/
String url();
default Deferred<Event<EventType>> waitForEvent(EventType event) {
return waitForEvent(event, (WaitForEventOptions) null);
}
default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {
WaitForEventOptions options = new WaitForEventOptions();
options.predicate = predicate;
return waitForEvent(event, options);
}
/**
* Returns the event data value.
* <p>
* Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy
* <p>
* value. Will throw an error if the webSocket is closed before the event is fired.
* @param event Event name, same one would pass into {@code webSocket.on(event)}.
*/
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
}
@@ -17,37 +17,42 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
* The Worker class represents a WebWorker. {@code worker}
* <p>
* 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
* <p>
* worker is gone.
* <p>
*/
public interface Worker {
enum EventType {
CLOSE,
}
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onClose(Consumer<Worker> handler);
void offClose(Consumer<Worker> handler);
class WaitForCloseOptions {
public Double timeout;
public WaitForCloseOptions withTimeout(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 pageFunction}
* <p>
* If the function passed to the {@code worker.evaluate} returns a Promise, then {@code worker.evaluate} would wait for the promise
* <p>
*
* <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 {@code worker.evaluate} returns a non-Serializable value, then {@code worker.evaluate} resolves to
* <p>
*
* <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}:
* <p>
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}, and bigint literals.
*
* @param pageFunction Function to be evaluated in the worker context
* @param arg Optional argument to pass to {@code pageFunction}
*/
@@ -57,19 +62,17 @@ public interface Worker {
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* <p>
* The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
* <p>
*
* <p> The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
* in-page object (JSHandle).
* <p>
* If the function passed to the {@code worker.evaluateHandle} returns a Promise, then {@code worker.evaluateHandle} would wait for
* <p>
*
* <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.
*
* @param pageFunction Function to be evaluated in the page context
* @param arg Optional argument to pass to {@code pageFunction}
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
String url();
Deferred<Event<EventType>> waitForEvent(EventType event);
}
@@ -31,6 +31,10 @@ class AccessibilityImpl implements Accessibility {
@Override
public AccessibilityNode snapshot(SnapshotOptions options) {
return page.withLogging("Accessibility.snapshot", () -> snapshotImpl(options));
}
private AccessibilityNode snapshotImpl(SnapshotOptions options) {
if (options == null) {
options = new SnapshotOptions();
}
@@ -21,10 +21,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -43,8 +43,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
PageImpl ownerPage;
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
enum EventType {
CLOSE,
PAGE,
}
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
@@ -54,17 +60,46 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
public void offClose(Consumer<BrowserContext> handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onPage(Consumer<Page> handler) {
listeners.add(EventType.PAGE, handler);
}
@Override
public void offPage(Consumer<Page> handler) {
listeners.remove(EventType.PAGE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Page waitForPage(Runnable code, WaitForPageOptions options) {
if (options == null) {
options = new WaitForPageOptions();
}
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
}
private void closeImpl() {
if (isClosedOrClosing) {
return;
}
@@ -80,13 +115,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void addCookies(List<AddCookie> cookies) {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params);
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params);
});
}
@Override
public void addInitScript(String script, Object arg) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script, arg));
}
private void addInitScriptImpl(String script, Object arg) {
// TODO: serialize arg
JsonObject params = new JsonObject();
if (isFunctionBody(script)) {
@@ -103,16 +143,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void clearCookies() {
sendMessage("clearCookies");
withLogging("BrowserContext.clearCookies", () -> sendMessage("clearCookies"));
}
@Override
public void clearPermissions() {
sendMessage("clearPermissions");
withLogging("BrowserContext.clearPermissions", () -> sendMessage("clearPermissions"));
}
@Override
public List<Cookie> cookies(List<String> urls) {
return withLogging("BrowserContext.cookies", () -> cookiesImpl(urls));
}
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = Collections.emptyList();
@@ -125,6 +169,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@@ -145,11 +193,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void exposeFunction(String name, Page.Function playwrightFunction) {
exposeBinding(name, (Page.Binding.Source source, Object... args) -> playwrightFunction.call(args));
withLogging("BrowserContext.exposeFunction",
() -> exposeBindingImpl(name, (Page.Binding.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
public void grantPermissions(List<String> permissions, GrantPermissionsOptions options) {
withLogging("BrowserContext.grantPermissions", () -> grantPermissionsImpl(permissions, options));
}
private void grantPermissionsImpl(List<String> permissions, GrantPermissionsOptions options) {
if (options == null) {
options = new GrantPermissionsOptions();
}
@@ -163,6 +216,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public PageImpl newPage() {
return withLogging("BrowserContext.newPage", () -> newPageImpl());
}
private PageImpl newPageImpl() {
if (ownerPage != null) {
throw new PlaywrightException("Please use browser.newContext()");
}
@@ -191,75 +248,89 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private void route(UrlMatcher matcher, Consumer<Route> handler) {
routes.add(matcher, handler);
if (routes.size() == 1) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
}
});
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
}
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
}
@Override
public void setDefaultNavigationTimeout(int timeout) {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
}
@Override
public void setDefaultTimeout(int timeout) {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
public void setDefaultTimeout(double timeout) {
withLogging("BrowserContext.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
}
@Override
public void setExtraHTTPHeaders(Map<String, String> headers) {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
withLogging("BrowserContext.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
});
}
@Override
public void setGeolocation(Geolocation geolocation) {
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params);
withLogging("BrowserContext.setGeolocation", () -> {
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params);
});
}
@Override
public void setOffline(boolean offline) {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params);
withLogging("BrowserContext.setOffline", () -> {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params);
});
}
@Override
public StorageState storageState(StorageStateOptions options) {
JsonElement json = sendMessage("storageState");
StorageState storageState = gson().fromJson(json, StorageState.class);
if (options != null && options.path != null) {
try {
Files.createDirectories(options.path.getParent());
try (FileWriter writer = new FileWriter(options.path.toFile())) {
writer.write(json.toString());
return withLogging("BrowserContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
StorageState storageState = gson().fromJson(json, StorageState.class);
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);
}
} catch (IOException e) {
throw new PlaywrightException("Failed to write storage state to file", e);
}
}
return storageState;
return storageState;
});
}
@Override
@@ -277,24 +348,26 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
unroute(new UrlMatcher(url), handler);
}
@Override
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
if (options == null) {
options = new WaitForEventOptions();
private class WaitableContextClose<R> extends WaitableEvent<EventType, R> {
WaitableContextClose() {
super(BrowserContextImpl.this.listeners, EventType.CLOSE);
}
@Override
public R get() {
throw new PlaywrightException("Context closed");
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
waitables.add(timeoutSettings.createWaitable(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
});
}
@Override
@@ -320,7 +393,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (browser != null) {
browser.contexts.remove(this);
}
listeners.notify(EventType.CLOSE, null);
listeners.notify(EventType.CLOSE, this);
}
}
}
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
@@ -37,22 +38,29 @@ class BrowserImpl extends ChannelOwner implements Browser {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
private boolean isConnected = true;
enum EventType {
DISCONNECTED,
}
BrowserImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
public void onDisconnected(Consumer<Browser> handler) {
listeners.add(EventType.DISCONNECTED, handler);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
public void offDisconnected(Consumer<Browser> handler) {
listeners.remove(EventType.DISCONNECTED, handler);
}
@Override
public void close() {
withLogging("Browser.close", () -> closeImpl());
}
private void closeImpl() {
try {
sendMessage("close");
} catch (PlaywrightException e) {
@@ -74,6 +82,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
public BrowserContextImpl newContext(NewContextOptions options) {
return withLogging("Browser.newContext", () -> newContextImpl(options));
}
private BrowserContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
}
@@ -86,18 +98,21 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.extraHTTPHeaders != null) {
params.remove("extraHTTPHeaders");
params.add("extraHTTPHeaders", Serialization.toProtocol(options.extraHTTPHeaders));
}
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;
}
contexts.add(context);
return context;
}
@Override
public Page newPage(NewPageOptions options) {
return withLogging("Browser.newPage", () -> newPageImpl(options));
}
private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
PageImpl page = context.newPage();
page.ownedContext = context;
@@ -122,7 +137,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, null);
listeners.notify(EventType.DISCONNECTED, this);
}
}
}
@@ -24,6 +24,7 @@ import com.microsoft.playwright.BrowserType;
import java.nio.file.Path;
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) {
@@ -32,6 +33,10 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override
public BrowserImpl launch(LaunchOptions options) {
return withLogging("BrowserType.launch", () -> launchImpl(options));
}
private BrowserImpl launchImpl(LaunchOptions options) {
if (options == null) {
options = new LaunchOptions();
}
@@ -44,20 +49,24 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
return initializer.get("executablePath").getAsString();
}
@Override
public BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
return withLogging("BrowserType.launchPersistentContext",
() -> launchPersistentContextImpl(userDataDir, options));
}
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
if (options == null) {
options = new LaunchPersistentContextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.extraHTTPHeaders != null) {
params.remove("extraHTTPHeaders");
params.add("extraHTTPHeaders", Serialization.toProtocol(options.extraHTTPHeaders));
}
params.addProperty("userDataDir", userDataDir.toString());
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
if (options.recordVideo != null) {
context.videosDir = options.recordVideo.dir;
}
return context;
}
public String name() {
@@ -18,13 +18,12 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Deferred;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
class ChannelOwner {
class ChannelOwner extends LoggingSupport {
final Connection connection;
private final ChannelOwner parent;
private final Map<String, ChannelOwner> objects = new HashMap<>();
@@ -80,22 +79,16 @@ class ChannelOwner {
return connection.sendMessage(guid, method, params);
}
void sendMessageNoWait(String method) {
sendMessageNoWait(method, new JsonObject());
}
void sendMessageNoWait(String method, JsonObject params) {
connection.sendMessageNoWait(guid, method, params);
}
@SuppressWarnings("unchecked")
<T> Deferred<T> toDeferred(Waitable waitable) {
return () -> {
<T> T runUntil(Runnable code, Waitable<T> waitable) {
try {
code.run();
while (!waitable.isDone()) {
connection.processOneMessage();
}
return (T) waitable.get();
};
return waitable.get();
} finally {
waitable.dispose();
}
}
void handleEvent(String event, JsonObject parameters) {
@@ -20,12 +20,9 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -74,17 +71,13 @@ public class Connection {
}
public JsonElement sendMessage(String guid, String method, JsonObject params) {
return (JsonElement) root.toDeferred(sendMessageAsync(guid, method, params)).get();
return root.runUntil(() -> {}, sendMessageAsync(guid, method, params));
}
public WaitableResult<JsonElement> sendMessageAsync(String guid, String method, JsonObject params) {
return internalSendMessage(guid, method, params);
}
public void sendMessageNoWait(String guid, String method, JsonObject params) {
internalSendMessage(guid, method, params);
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
int id = ++lastId;
WaitableResult<JsonElement> result = new WaitableResult<>();
@@ -38,7 +38,7 @@ class DeviceDescriptorImpl implements DeviceDescriptor {
}
private ViewportImpl viewport;
private String userAgent;
private int deviceScaleFactor;
private double deviceScaleFactor;
private boolean isMobile;
private boolean hasTouch;
private String defaultBrowserType;
@@ -54,7 +54,7 @@ class DeviceDescriptorImpl implements DeviceDescriptor {
}
@Override
public int deviceScaleFactor() {
public double deviceScaleFactor() {
return deviceScaleFactor;
}
@@ -20,25 +20,25 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Dialog;
import com.microsoft.playwright.PlaywrightException;
public class DialogImpl extends ChannelOwner implements Dialog {
private boolean handled;
class DialogImpl extends ChannelOwner implements Dialog {
DialogImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void accept(String promptText) {
handled = true;
JsonObject params = new JsonObject();
if (promptText != null)
params.addProperty("promptText", promptText);
sendMessageNoWait("accept", params);
withLogging("Dialog.accept", () -> {
JsonObject params = new JsonObject();
if (promptText != null) {
params.addProperty("promptText", promptText);
}
sendMessage("accept", params);
});
}
@Override
public void dismiss() {
handled = true;
sendMessageNoWait("dismiss");
withLogging("Dialog.dismiss", () -> sendMessage("dismiss"));
}
@Override
@@ -61,8 +61,4 @@ public class DialogImpl extends ChannelOwner implements Dialog {
default: throw new PlaywrightException("Unexpected dialog type: " + initializer.get("type").getAsString());
}
}
boolean isHandled() {
return handled;
}
}
@@ -40,38 +40,48 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public InputStream createReadStream() {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
return withLogging("Download.createReadStream", () -> {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
});
}
@Override
public void delete() {
sendMessage("delete");
withLogging("Download.delete", () -> {
sendMessage("delete");
});
}
@Override
public String failure() {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
return withLogging("Download.failure", () -> {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
});
}
@Override
public Path path() {
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
return withLogging("Download.path", () -> {
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
});
}
@Override
public void saveAs(Path path) {
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
withLogging("Download.saveAs", () -> {
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
});
}
}
@@ -16,16 +16,13 @@
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.Deferred;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Frame;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
@@ -46,67 +43,81 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public ElementHandle querySelector(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
return withLogging("ElementHandle.querySelector", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
});
}
@Override
public List<ElementHandle> querySelectorAll(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
return withLogging("ElementHandle.<", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
});
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
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);
return deserialize(value);
return withLogging("ElementHandle.evalOnSelector", () -> {
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);
return deserialize(value);
});
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
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);
return deserialize(value);
return withLogging("ElementHandle.evalOnSelectorAll", () -> {
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);
return deserialize(value);
});
}
@Override
public BoundingBox boundingBox() {
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
return withLogging("ElementHandle.boundingBox", () -> {
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
});
}
@Override
public void check(CheckOptions options) {
withLogging("ElementHandle.check", () -> checkImpl(options));
}
private void checkImpl(CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
@@ -116,6 +127,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void click(ClickOptions options) {
withLogging("ElementHandle.click", () -> clickImpl(options));
}
private void clickImpl(ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
@@ -135,6 +150,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public Frame contentFrame() {
return withLogging("ElementHandle.contentFrame", () -> contentFrameImpl());
}
private Frame contentFrameImpl() {
JsonObject json = sendMessage("contentFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
@@ -144,6 +163,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void dblclick(DblclickOptions options) {
withLogging("ElementHandle.dblclick", () -> dblclickImpl(options));
}
private void dblclickImpl(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
@@ -163,14 +186,20 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void dispatchEvent(String type, Object eventInit) {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params);
withLogging("ElementHandle.dispatchEvent", () -> {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params);
});
}
@Override
public void fill(String value, FillOptions options) {
withLogging("ElementHandle.fill", () -> fillImpl(value, options));
}
private void fillImpl(String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
@@ -181,50 +210,111 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void focus() {
sendMessage("focus");
withLogging("ElementHandle.focus", () -> sendMessage("focus"));
}
@Override
public String getAttribute(String name) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
return withLogging("ElementHandle.getAttribute", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
}
@Override
public void hover(HoverOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("hover", params);
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);
});
}
@Override
public String innerHTML() {
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
return withLogging("ElementHandle.innerHTML", () -> {
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
});
}
@Override
public String innerText() {
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
return withLogging("ElementHandle.innerText", () -> {
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
});
}
@Override
public boolean isChecked() {
return withLogging("ElementHandle.isChecked", () -> {
JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isDisabled() {
return withLogging("ElementHandle.isDisabled", () -> {
JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isEditable() {
return withLogging("ElementHandle.isEditable", () -> {
JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isEnabled() {
return withLogging("ElementHandle.isEnabled", () -> {
JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isHidden() {
return withLogging("ElementHandle.isHidden", () -> {
JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isVisible() {
return withLogging("ElementHandle.isVisible", () -> {
JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public Frame ownerFrame() {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
return withLogging("ElementHandle.ownerFrame", () -> {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
});
}
@Override
public void press(String key, PressOptions options) {
withLogging("ElementHandle.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
@@ -239,6 +329,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
}
private byte[] screenshotImpl(ScreenshotOptions options) {
if (options == null) {
options = new ScreenshotOptions();
}
@@ -270,6 +364,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
}
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
@@ -302,12 +400,18 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
private List<String> selectOption(JsonObject params) {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
return withLogging("ElementHandle.selectOption", () -> {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
});
}
@Override
public void selectText(SelectTextOptions options) {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
}
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
@@ -322,6 +426,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -332,17 +440,39 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void tap(TapOptions options) {
withLogging("ElementHandle.tap", () -> tapImpl(options));
}
private void tapImpl(TapOptions options) {
if (options == null) {
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);
}
@Override
public String textContent() {
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
return withLogging("ElementHandle.textContent", () -> textContentImpl());
}
private String textContentImpl() {
return withLogging("ElementHandle.textContent", () -> {
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
}
@Override
public void type(String text, TypeOptions options) {
withLogging("ElementHandle.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
@@ -353,6 +483,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void uncheck(UncheckOptions options) {
withLogging("ElementHandle.uncheck", () -> uncheckImpl(options));
}
private void uncheckImpl(UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
@@ -361,13 +495,17 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public Deferred<Void> waitForElementState(ElementState state, WaitForElementStateOptions options) {
public void waitForElementState(ElementState state, WaitForElementStateOptions options) {
withLogging("ElementHandle.waitForElementState", () -> waitForElementStateImpl(state, options));
}
private void waitForElementStateImpl(ElementState state, WaitForElementStateOptions options) {
if (options == null) {
options = new WaitForElementStateOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
sendMessage("waitForElementState", params);
}
private static String toProtocol(ElementState state) {
@@ -378,7 +516,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public Deferred<ElementHandle> waitForSelector(String selector, WaitForSelectorOptions options) {
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("ElementHandle.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
private ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
if (options == null) {
options = new WaitForSelectorOptions();
}
@@ -386,17 +528,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
params.remove("state");
params.addProperty("state", toProtocol(options.state));
params.addProperty("selector", selector);
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
}
public String createSelectorForTest(String name) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("createSelectorForTest", params).getAsJsonObject();
if (json.has("value")) {
return json.get("value").getAsString();
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return null;
return connection.getExistingObject(element.get("guid").getAsString());
}
private static String toProtocol(WaitForSelectorOptions.State state) {
@@ -16,23 +16,20 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Page;
import java.io.File;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class FileChooserImpl implements FileChooser {
private final PageImpl page;
private final ElementHandle element;
private final ElementHandleImpl element;
private final boolean isMultiple;
FileChooserImpl(PageImpl page, ElementHandle element, boolean isMultiple) {
FileChooserImpl(PageImpl page, ElementHandleImpl element, boolean isMultiple) {
this.page = page;
this.element = element;
this.isMultiple = isMultiple;
@@ -60,6 +57,7 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
element.setInputFiles(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class));
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -16,21 +16,21 @@
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.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
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.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 {
@@ -69,6 +69,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ElementHandle querySelector(String selector) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector));
}
ElementHandleImpl querySelectorImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
@@ -81,6 +85,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
}
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
@@ -97,6 +105,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg));
}
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
@@ -109,6 +121,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelectorAll", () -> evalOnSelectorAllImpl(selector, pageFunction, arg));
}
Object evalOnSelectorAllImpl(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
@@ -120,13 +136,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public ElementHandle addScriptTag(AddScriptTagScript options) {
public ElementHandle addScriptTag(AddScriptTagOptions options){
return withLogging("Frame.addScriptTag", () -> addScriptTagImpl(options));
}
ElementHandle addScriptTagImpl(AddScriptTagOptions options) {
if (options == null) {
options = new AddScriptTagScript();
options = new AddScriptTagOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject jsonOptions = gson().toJsonTree(options).getAsJsonObject();
if (options.path != null) {
params.remove("path");
jsonOptions.remove("path");
byte[] encoded;
try {
encoded = Files.readAllBytes(options.path);
@@ -135,20 +155,24 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
String content = new String(encoded, StandardCharsets.UTF_8);
content += "//# sourceURL=" + options.path.toString().replace("\n", "");
params.addProperty("content", content);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", params);
JsonElement json = sendMessage("addScriptTag", jsonOptions);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public ElementHandle addStyleTag(AddStyleTagStyle options) {
public ElementHandle addStyleTag(AddStyleTagOptions options){
return withLogging("Frame.addStyleTag", () -> addStyleTagImpl(options));
}
ElementHandle addStyleTagImpl(AddStyleTagOptions options) {
if (options == null) {
options = new AddStyleTagStyle();
options = new AddStyleTagOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject jsonOptions = gson().toJsonTree(options).getAsJsonObject();
if (options.path != null) {
params.remove("path");
jsonOptions.remove("path");
byte[] encoded;
try {
encoded = Files.readAllBytes(options.path);
@@ -157,14 +181,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
String content = new String(encoded, StandardCharsets.UTF_8);
content += "/*# sourceURL=" + options.path.toString().replace("\n", "") + "*/";
params.addProperty("content", content);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addStyleTag", params);
JsonElement json = sendMessage("addStyleTag", jsonOptions);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public void check(String selector, CheckOptions options) {
public void check(String selector, CheckOptions options){
withLogging("Frame.check", () -> checkImpl(selector, options));
}
void checkImpl(String selector, CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
@@ -180,6 +208,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void click(String selector, ClickOptions options) {
withLogging("Frame.click", () -> clickImpl(selector, options));
}
void clickImpl(String selector, ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
@@ -201,11 +233,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public String content() {
return withLogging("Frame.content", () -> contentImpl());
}
String contentImpl() {
return sendMessage("content").getAsJsonObject().get("value").getAsString();
}
@Override
public void dblclick(String selector, DblclickOptions options) {
withLogging("Frame.dblclick", () -> dblclickImpl(selector, options));
}
void dblclickImpl(String selector, DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
@@ -227,6 +267,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Frame.dispatchEvent", () -> dispatchEventImpl(selector, type, eventInit, options));
}
void dispatchEventImpl(String selector, String type, Object eventInit, DispatchEventOptions options) {
if (options == null) {
options = new DispatchEventOptions();
}
@@ -239,6 +283,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Object evaluate(String expression, Object arg) {
return withLogging("Frame.evaluate", () -> evaluateImpl(expression, arg));
}
Object evaluateImpl(String expression, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
@@ -251,6 +299,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Frame.evaluateHandle", () -> evaluateHandleImpl(pageFunction, arg));
}
JSHandle evaluateHandleImpl(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
@@ -262,6 +314,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void fill(String selector, String value, FillOptions options) {
withLogging("Frame.fill", () -> fillImpl(selector, value, options));
}
void fillImpl(String selector, String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
@@ -273,6 +329,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void focus(String selector, FocusOptions options) {
withLogging("Frame.focus", () -> focusImpl(selector, options));
}
void focusImpl(String selector, FocusOptions options) {
if (options == null) {
options = new FocusOptions();
}
@@ -283,12 +343,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ElementHandle frameElement() {
return withLogging("Frame.frameElement", () -> frameElementImpl());
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
}
@Override
public String getAttribute(String selector, String name, GetAttributeOptions options) {
return withLogging("Frame.getAttribute", () -> getAttributeImpl(selector, name, options));
}
String getAttributeImpl(String selector, String name, GetAttributeOptions options) {
if (options == null) {
options = new GetAttributeOptions();
}
@@ -304,6 +373,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> navigateImpl(url, options));
}
ResponseImpl navigateImpl(String url, NavigateOptions options) {
if (options == null) {
options = new NavigateOptions();
}
@@ -323,6 +396,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void hover(String selector, HoverOptions options) {
withLogging("Frame.hover", () -> hoverImpl(selector, options));
}
void hoverImpl(String selector, HoverOptions options) {
if (options == null) {
options = new HoverOptions();
}
@@ -333,6 +410,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
}
String innerHTMLImpl(String selector, InnerHTMLOptions options) {
if (options == null) {
options = new InnerHTMLOptions();
}
@@ -344,6 +425,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public String innerText(String selector, InnerTextOptions options) {
return withLogging("Frame.innerText", () -> innerTextImpl(selector, options));
}
String innerTextImpl(String selector, InnerTextOptions options) {
if (options == null) {
options = new InnerTextOptions();
}
@@ -353,11 +438,101 @@ public class FrameImpl extends ChannelOwner implements Frame {
return json.get("value").getAsString();
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
}
boolean isCheckedImpl(String selector, IsCheckedOptions options) {
if (options == null) {
options = new IsCheckedOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isChecked", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isDetached() {
return isDetached;
}
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled", () -> isDisabledImpl(selector, options));
}
boolean isDisabledImpl(String selector, IsDisabledOptions options) {
if (options == null) {
options = new IsDisabledOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isDisabled", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable", () -> isEditableImpl(selector, options));
}
boolean isEditableImpl(String selector, IsEditableOptions options) {
if (options == null) {
options = new IsEditableOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEditable", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled", () -> isEnabledImpl(selector, options));
}
boolean isEnabledImpl(String selector, IsEnabledOptions options) {
if (options == null) {
options = new IsEnabledOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEnabled", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden", () -> isHiddenImpl(selector, options));
}
boolean isHiddenImpl(String selector, IsHiddenOptions options) {
if (options == null) {
options = new IsHiddenOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isHidden", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
}
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
if (options == null) {
options = new IsVisibleOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isVisible", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public String name() {
return name;
@@ -375,6 +550,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void press(String selector, String key, PressOptions options) {
withLogging("Frame.press", () -> pressImpl(selector, key, options));
}
void pressImpl(String selector, String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
@@ -386,6 +565,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<String> selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
@@ -399,6 +582,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, ElementHandle[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
@@ -429,6 +616,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Frame.setContent", () -> setContentImpl(html, options));
}
void setContentImpl(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
@@ -441,11 +632,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, 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) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -457,6 +656,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void tap(String selector, TapOptions options) {
withLogging("Frame.tap", () -> tapImpl(selector, options));
}
void tapImpl(String selector, TapOptions options) {
if (options == null) {
options = new TapOptions();
}
@@ -471,6 +673,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public String textContent(String selector, TextContentOptions options) {
return withLogging("Frame.textContent", () -> textContentImpl(selector, options));
}
String textContentImpl(String selector, TextContentOptions options) {
if (options == null) {
options = new TextContentOptions();
}
@@ -481,12 +687,20 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public String title() {
return withLogging("Frame.title", () -> titleImpl());
}
String titleImpl() {
JsonElement json = sendMessage("title");
return json.getAsJsonObject().get("value").getAsString();
}
@Override
public void type(String selector, String text, TypeOptions options) {
withLogging("Frame.type", () -> typeImpl(selector, text, options));
}
void typeImpl(String selector, String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
@@ -498,6 +712,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void uncheck(String selector, UncheckOptions options) {
withLogging("Frame.uncheck", () -> uncheckImpl(selector, options));
}
void uncheckImpl(String selector, UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
@@ -512,7 +730,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Deferred<JSHandle> waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Frame.waitForFunction", () -> waitForFunctionImpl(pageFunction, arg, options));
}
JSHandle waitForFunctionImpl(String pageFunction, Object arg, WaitForFunctionOptions options) {
if (options == null) {
options = new WaitForFunctionOptions();
}
@@ -520,15 +742,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
Waitable<JSHandle> handle = sendMessageAsync("waitForFunction", params).apply(json -> {
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
return connection.getExistingObject(element.get("guid").getAsString());
});
return toDeferred(handle);
JsonElement json = sendMessage("waitForFunction", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
return connection.getExistingObject(element.get("guid").getAsString());
}
@Override
public Deferred<Void> waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withLogging("Frame.waitForLoadState", () -> waitForLoadStateImpl(state, options));
}
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
if (options == null) {
options = new WaitForLoadStateOptions();
}
@@ -540,10 +764,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
waitables.add(new WaitForLoadStateHelper(state));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
runUntil(() -> {}, new WaitableRace<>(waitables));
}
private class WaitForLoadStateHelper implements Waitable<Void>, Listener<InternalEventType> {
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
private final LoadState expectedState;
private boolean isDone;
@@ -556,9 +780,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public void handle(Event<InternalEventType> event) {
assert event.type() == InternalEventType.LOADSTATE;
if (expectedState.equals(event.data())) {
public void accept(LoadState state) {
if (expectedState.equals(state)) {
isDone = true;
dispose();
}
@@ -578,7 +801,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
}
private class WaitForNavigationHelper implements Waitable<Response>, Listener<InternalEventType> {
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
private final UrlMatcher matcher;
private final LoadState expectedLoadState;
private WaitForLoadStateHelper loadStateHelper;
@@ -593,9 +816,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public void handle(Event<InternalEventType> event) {
assert InternalEventType.NAVIGATED == event.type();
JsonObject params = (JsonObject) event.data();
public void accept(JsonObject params) {
if (!matcher.test(params.get("url").getAsString())) {
return;
}
@@ -646,7 +867,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Deferred<Response> waitForNavigation(WaitForNavigationOptions options) {
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
if (options == null) {
options = new WaitForNavigationOptions();
}
@@ -660,7 +885,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
return runUntil(code, new WaitableRace<>(waitables));
}
private static String toProtocol(WaitForSelectorOptions.State state) {
@@ -668,7 +893,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Deferred<ElementHandle> waitForSelector(String selector, WaitForSelectorOptions options) {
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Frame.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
if (options == null) {
options = new WaitForSelectorOptions();
}
@@ -678,19 +907,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.remove("state");
params.addProperty("state", toProtocol(options.state));
}
Waitable<ElementHandle> handle = sendMessageAsync("waitForSelector", params).apply(json -> {
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
});
return toDeferred(handle);
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
}
@Override
public Deferred<Void> waitForTimeout(int timeout) {
return toDeferred(new WaitableTimeout<Void>(timeout) {
public void waitForTimeout(double timeout) {
withLogging("Frame.waitForTimeout", () -> waitForTimeoutImpl(timeout));
}
void waitForTimeoutImpl(double timeout) {
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
@Override
public Void get() {
// Override to not throw.
@@ -16,7 +16,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
@@ -29,8 +28,11 @@ 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;
public JSHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.preview = initializer.get("preview").getAsString();
}
@Override
@@ -40,56 +42,79 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
@Override
public void dispose() {
sendMessage("dispose");
withLogging("JSHandle.dispose", () -> sendMessage("dispose"));
}
@Override
public Object evaluate(String pageFunction, Object arg) {
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);
return deserialize(value);
return withLogging("JSHandle.evaluate", () -> {
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);
return deserialize(value);
});
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
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());
return withLogging("JSHandle.evaluateHandle", () -> {
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());
});
}
@Override
public Map<String, JSHandle> getProperties() {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
return withLogging("JSHandle.getProperties", () -> {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
});
}
@Override
public JSHandle getProperty(String propertyName) {
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
return withLogging("JSHandle.getProperty", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
});
}
@Override
public Object jsonValue() {
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("JSHandle.jsonValue", () -> {
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
void handleEvent(String event, JsonObject parameters) {
if ("previewUpdated".equals(event)) {
preview = parameters.get("preview").getAsString();
}
super.handleEvent(event, parameters);
}
@Override
public String toString() {
return preview;
}
}
@@ -19,7 +19,9 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Keyboard;
class KeyboardImpl implements Keyboard {
import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl extends LoggingSupport implements Keyboard {
private final ChannelOwner page;
KeyboardImpl(ChannelOwner page) {
@@ -28,42 +30,56 @@ class KeyboardImpl implements Keyboard {
@Override
public void down(String key) {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
});
}
@Override
public void insertText(String text) {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
});
}
@Override
public void press(String key, int delay) {
JsonObject params = new JsonObject();
params.addProperty("key", key);
if (delay != 0) {
params.addProperty("delay", delay);
public void press(String key, PressOptions options) {
withLogging("Keyboard.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardPress", params);
}
@Override
public void type(String text, int delay) {
JsonObject params = new JsonObject();
params.addProperty("text", text);
if (delay != 0) {
params.addProperty("delay", delay);
@Override
public void type(String text, TypeOptions options) {
withLogging("Keyboard.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardType", params);
}
@Override
public void up(String key) {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
});
}
}
@@ -16,43 +16,28 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Deferred;
import com.microsoft.playwright.Event;
import com.microsoft.playwright.Listener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
class ListenerCollection <EventType> {
private final HashMap<EventType, List<Listener<EventType>>> listeners = new HashMap<>();
private final HashMap<EventType, List<Consumer<?>>> listeners = new HashMap<>();
void notify(EventType eventType, Object param) {
List<Listener<EventType>> list = listeners.get(eventType);
<T> void notify(EventType eventType, T param) {
List<Consumer<?>> list = listeners.get(eventType);
if (list == null) {
return;
}
Event<EventType> event = new Event<EventType>() {
@Override
public EventType type() {
return eventType;
}
@Override
public Object data() {
return param;
}
};
for (Listener<EventType> listener: new ArrayList<>(list)) {
listener.handle(event);
for (Consumer<?> listener: new ArrayList<>(list)) {
((Consumer<T>) listener).accept(param);
}
}
void add(EventType type, Listener<EventType> listener) {
List<Listener<EventType>> list = listeners.get(type);
void add(EventType type, Consumer<?> listener) {
List<Consumer<?>> list = listeners.get(type);
if (list == null) {
list = new ArrayList<>();
listeners.put(type, list);
@@ -60,8 +45,8 @@ class ListenerCollection <EventType> {
list.add(listener);
}
void remove(EventType type, Listener<EventType> listener) {
List<Listener<EventType>> list = listeners.get(type);
void remove(EventType type, Consumer<?> listener) {
List<Consumer<?>> list = listeners.get(type);
if (list == null) {
return;
}
@@ -0,0 +1,66 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.function.Supplier;
class LoggingSupport {
private static final boolean isEnabled;
static {
String debug = System.getenv("DEBUG");
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"));
}
void withLogging(String apiName, Runnable code) {
withLogging(apiName, () -> {
code.run();
return null;
});
}
<T> T withLogging(String apiName, Supplier<T> code) {
if (isEnabled) {
logApi("=> " + apiName + " started");
}
boolean success = false;
try {
T result = code.get();
success = true;
return result;
} finally {
if (isEnabled) {
logApi("<= " + apiName + (success ? " succeeded" : " failed"));
}
}
}
private void logApi(String message) {
// This matches log format produced by the server.
String timestamp = timestampFormat.format(new Date());
System.err.println(timestamp + " pw:api " + message);
}
}
@@ -32,7 +32,11 @@ class MouseImpl implements Mouse {
}
@Override
public void click(int x, int y, ClickOptions options) {
public void click(double x, double y, ClickOptions options) {
page.withLogging("Mouse.click", () -> clickImpl(x, y, options));
}
private void clickImpl(double x, double y, ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
@@ -47,7 +51,11 @@ class MouseImpl implements Mouse {
}
@Override
public void dblclick(int x, int y, DblclickOptions options) {
public void dblclick(double x, double y, DblclickOptions options) {
page.withLogging("Mouse.dblclick", () -> dblclickImpl(x, y, options));
}
private void dblclickImpl(double x, double y, DblclickOptions options) {
ClickOptions clickOptions;
if (options == null) {
clickOptions = new ClickOptions();
@@ -60,6 +68,10 @@ class MouseImpl implements Mouse {
@Override
public void down(DownOptions options) {
page.withLogging("Mouse.down", () -> downImpl(options));
}
private void downImpl(DownOptions options) {
if (options == null) {
options = new DownOptions();
}
@@ -68,7 +80,11 @@ class MouseImpl implements Mouse {
}
@Override
public void move(int x, int y, MoveOptions options) {
public void move(double x, double y, MoveOptions options) {
page.withLogging("Mouse.move", () -> moveImpl(x, y, options));
}
private void moveImpl(double x, double y, MoveOptions options) {
if (options == null) {
options = new MoveOptions();
}
@@ -80,6 +96,10 @@ class MouseImpl implements Mouse {
@Override
public void up(UpOptions options) {
page.withLogging("Mouse.up", () -> upImpl(options));
}
private void upImpl(UpOptions options) {
if (options == null) {
options = new UpOptions();
}
File diff suppressed because it is too large Load Diff
@@ -24,9 +24,11 @@ import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.io.*;
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;
@@ -33,6 +33,7 @@ public class RequestImpl extends ChannelOwner implements Request {
private RequestImpl redirectedTo;
final Map<String, String> headers = new HashMap<>();
RequestFailure failure;
RequestTiming timing;
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -107,11 +108,13 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public Response response() {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
return withLogging("Request.response", () -> {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
});
}
@Override
@@ -45,21 +45,26 @@ 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);
}
@Override
public byte[] body() {
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
return withLogging("Response.body", () -> {
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
});
}
@Override
public String finished() {
JsonObject json = sendMessage("finished").getAsJsonObject();
if (json.has("error")) {
return json.get("error").getAsString();
}
return null;
return withLogging("Response.finished", () -> {
JsonObject json = sendMessage("finished").getAsJsonObject();
if (json.has("error")) {
return json.get("error").getAsString();
}
return null;
});
}
@Override
@@ -36,72 +36,82 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void abort(String errorCode) {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessage("abort", params);
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessage("abort", params);
});
}
@Override
public void continue_(ContinueOverrides overrides) {
if (overrides == null) {
overrides = new ContinueOverrides();
public void continue_(ContinueOptions options) {
withLogging("Route.continue", () -> continueImpl(options));
}
private void continueImpl(ContinueOptions options) {
if (options == null) {
options = new ContinueOptions();
}
JsonObject params = new JsonObject();
if (overrides.url != null) {
params.addProperty("url", overrides.url);
if (options.url != null) {
params.addProperty("url", options.url);
}
if (overrides.method != null) {
params.addProperty("method", overrides.method);
if (options.method != null) {
params.addProperty("method", options.method);
}
if (overrides.headers != null) {
params.add("headers", Serialization.toProtocol(overrides.headers));
if (options.headers != null) {
params.add("headers", Serialization.toProtocol(options.headers));
}
if (overrides.postData != null) {
String base64 = Base64.getEncoder().encodeToString(overrides.postData);
if (options.postData != null) {
String base64 = Base64.getEncoder().encodeToString(options.postData);
params.addProperty("postData", base64);
}
sendMessage("continue", params);
}
@Override
public void fulfill(FulfillResponse response) {
if (response == null) {
response = new FulfillResponse();
public void fulfill(FulfillOptions options) {
withLogging("Route.fulfill", () -> fulfillImpl(options));
}
private void fulfillImpl(FulfillOptions options) {
if (options == null) {
options = new FulfillOptions();
}
int status = response.status == 0 ? 200 : response.status;
int status = options.status == null ? 200 : options.status;
String body = "";
boolean isBase64 = false;
int length = 0;
if (response.path != null) {
if (options.path != null) {
try {
byte[] buffer = Files.readAllBytes(response.path);
byte[] buffer = Files.readAllBytes(options.path);
body = Base64.getEncoder().encodeToString(buffer);
isBase64 = true;
length = buffer.length;
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file: " + response.path, e);
throw new PlaywrightException("Failed to read from file: " + options.path, e);
}
} else if (response.body != null) {
body = response.body;
} else if (options.body != null) {
body = options.body;
isBase64 = false;
length = body.getBytes().length;
} else if (response.bodyBytes != null) {
body = Base64.getEncoder().encodeToString(response.bodyBytes);
} else if (options.bodyBytes != null) {
body = Base64.getEncoder().encodeToString(options.bodyBytes);
isBase64 = true;
length = response.bodyBytes.length;
length = options.bodyBytes.length;
}
Map<String, String> headers = new LinkedHashMap<>();
if (response.headers != null) {
for (Map.Entry<String, String> h : response.headers.entrySet()) {
if (options.headers != null) {
for (Map.Entry<String, String> h : options.headers.entrySet()) {
headers.put(h.getKey().toLowerCase(), h.getValue());
}
}
if (response.contentType != null) {
headers.put("content-type", response.contentType);
} else if (response.path != null) {
headers.put("content-type", Utils.mimeType(response.path));
if (options.contentType != null) {
headers.put("content-type", options.contentType);
} else if (options.path != null) {
headers.put("content-type", Utils.mimeType(options.path));
}
if (length != 0 && !headers.containsKey("content-length")) {
headers.put("content-length", Integer.toString(length));
@@ -34,6 +34,10 @@ class SelectorsImpl extends ChannelOwner implements Selectors {
@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) {
if (options == null) {
options = new RegisterOptions();
}
@@ -41,6 +41,7 @@ class Serialization {
.registerTypeAdapter(Page.EmulateMediaParams.Media.class, new MediaSerializer())
.registerTypeAdapter(Optional.class, new OptionalSerializer())
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeHierarchyAdapter(Map.class, new StringMapSerializer())
.registerTypeAdapter(Path.class, new PathSerializer()).create();
}
return gson;
@@ -275,6 +276,16 @@ class Serialization {
}
}
private static class StringMapSerializer implements JsonSerializer<Map<String, String>> {
@Override
public JsonElement serialize(Map<String, String> src, Type typeOfSrc, JsonSerializationContext context) {
if (!"java.util.Map<java.lang.String, java.lang.String>".equals(typeOfSrc.getTypeName())) {
throw new PlaywrightException("Unexpected map type: " + typeOfSrc);
}
return toProtocol(src);
}
}
private static class MediaSerializer implements JsonSerializer<Page.EmulateMediaParams.Media> {
@Override
public JsonElement serialize(Page.EmulateMediaParams.Media src, Type typeOfSrc, JsonSerializationContext context) {
@@ -20,8 +20,8 @@ class TimeoutSettings {
private static final int DEFAULT_TIMEOUT_MS = 30_000;
private final TimeoutSettings parent;
private Integer defaultTimeout ;
private Integer defaultNavigationTimeout;
private Double defaultTimeout ;
private Double defaultNavigationTimeout;
TimeoutSettings() {
this(null);
@@ -30,15 +30,15 @@ class TimeoutSettings {
this.parent = parent;
}
void setDefaultTimeout(int timeout) {
void setDefaultTimeout(double timeout) {
defaultTimeout = timeout;
}
void setDefaultNavigationTimeout(int timeout) {
void setDefaultNavigationTimeout(double timeout) {
defaultNavigationTimeout = timeout;
}
int timeout(Integer timeout) {
double timeout(Double timeout) {
if (timeout != null) {
return timeout;
}
@@ -51,7 +51,7 @@ class TimeoutSettings {
return DEFAULT_TIMEOUT_MS;
}
int navigationTimeout(Integer timeout) {
double navigationTimeout(Double timeout) {
if (timeout != null) {
return timeout;
}
@@ -67,7 +67,7 @@ class TimeoutSettings {
return DEFAULT_TIMEOUT_MS;
}
<T> Waitable<T> createWaitable(Integer timeout) {
<T> Waitable<T> createWaitable(Double timeout) {
if (timeout != null && timeout == 0) {
return new WaitableNever<>();
}
@@ -27,10 +27,12 @@ class TouchscreenImpl implements Touchscreen {
}
@Override
public void tap(int x, int y) {
JsonObject params = new JsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("touchscreenTap", params);
public void tap(double x, double y) {
page.withLogging("Touchscreen.tap", () -> {
JsonObject params = new JsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("touchscreenTap", params);
});
}
}
@@ -37,8 +37,7 @@ public class Transport {
DataInputStream in = new DataInputStream(new BufferedInputStream(input));
readerThread = new ReaderThread(in, incoming);
readerThread.start();
DataOutputStream out = new DataOutputStream(output);
writerThread = new WriterThread(out, outgoing);
writerThread = new WriterThread(output, outgoing);
writerThread.start();
}
@@ -125,17 +124,17 @@ class ReaderThread extends Thread {
}
class WriterThread extends Thread {
final DataOutputStream out;
final OutputStream out;
private final BlockingQueue<String> queue;
private static void writeIntLE(DataOutputStream out, int v) throws IOException {
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(DataOutputStream out, BlockingQueue<String> queue) {
WriterThread(OutputStream out, BlockingQueue<String> queue) {
this.out = out;
this.queue = queue;
}
@@ -158,8 +157,8 @@ class WriterThread extends Thread {
}
private void sendMessage(String message) throws IOException {
int len = message.length();
writeIntLE(out, len);
out.writeBytes(message);
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
writeIntLE(out, bytes.length);
out.write(bytes);
}
}
@@ -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,8 +14,29 @@
* limitations under the License.
*/
package com.microsoft.playwright;
package com.microsoft.playwright.impl;
public interface Listener<EventType> {
void handle(Event<EventType> event);
import com.microsoft.playwright.Video;
import java.nio.file.Path;
class VideoImpl implements Video {
private final PageImpl page;
private Path fullPath;
VideoImpl(PageImpl page) {
this.page = page;
}
void setRelativePath(String path) {
fullPath = page.context().videosDir.resolve(path);
}
@Override
public Path path() {
while (fullPath == null) {
page.connection.processOneMessage();
}
return fullPath;
}
}
@@ -17,22 +17,21 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Event;
import com.microsoft.playwright.Listener;
import com.microsoft.playwright.Page;
import java.util.function.Consumer;
import java.util.function.Predicate;
class WaitableEvent<EventType> implements Waitable<Event<EventType>>, Listener<EventType> {
class WaitableEvent<EventType, T> implements Waitable<T>, Consumer<T> {
final ListenerCollection<EventType> listeners;
private final EventType type;
private final Predicate<Event<EventType>> predicate;
private Event<EventType> event;
private final Predicate<T> predicate;
private T eventArg;
WaitableEvent(ListenerCollection<EventType> listeners, EventType type) {
this(listeners, type, null);
}
WaitableEvent(ListenerCollection<EventType> listeners, EventType type, Predicate<Event<EventType>> predicate) {
WaitableEvent(ListenerCollection<EventType> listeners, EventType type, Predicate<T> predicate) {
this.listeners = listeners;
this.type = type;
this.predicate = predicate;
@@ -40,19 +39,18 @@ class WaitableEvent<EventType> implements Waitable<Event<EventType>>, Listener<E
}
@Override
public void handle(Event<EventType> event) {
assert type.equals(event.type());
if (predicate != null && !predicate.test(event)) {
public void accept(T eventArg) {
if (predicate != null && !predicate.test(eventArg)) {
return;
}
this.event = event;
this.eventArg = eventArg;
dispose();
}
@Override
public boolean isDone() {
return event != null;
return eventArg != null;
}
@Override
@@ -61,7 +59,7 @@ class WaitableEvent<EventType> implements Waitable<Event<EventType>>, Listener<E
}
@Override
public Event<EventType> get() {
return event;
public T get() {
return eventArg;
}
}
@@ -16,7 +16,6 @@
package com.microsoft.playwright.impl;
import java.util.Arrays;
import java.util.Collection;
class WaitableRace<T> implements Waitable<T> {
@@ -20,9 +20,9 @@ import com.microsoft.playwright.PlaywrightException;
class WaitableTimeout<T> implements Waitable<T> {
private final long deadline;
private final int timeout;
private final double timeout;
WaitableTimeout(int millis) {
WaitableTimeout(double millis) {
timeout = millis;
deadline = System.nanoTime() + (long) millis * 1_000_000;
}
@@ -34,7 +34,11 @@ class WaitableTimeout<T> implements Waitable<T> {
@Override
public T get() {
throw new PlaywrightException("Timeout " + timeout + "ms exceeded");
String timeoutStr = Double.toString(timeout);
if (timeoutStr.endsWith(".0")) {
timeoutStr = timeoutStr.substring(0, timeoutStr.length() - 2);
}
throw new PlaywrightException("Timeout " + timeoutStr + "ms exceeded");
}
@Override
@@ -24,25 +24,87 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
class WebSocketImpl extends ChannelOwner implements WebSocket {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
private final PageImpl page;
private boolean isClosed;
public WebSocketImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
enum EventType {
CLOSE,
FRAMERECEIVED,
FRAMESENT,
SOCKETERROR,
}
WebSocketImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
page = (PageImpl) parent;
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
public void onClose(Consumer<WebSocket> handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
public void offClose(Consumer<WebSocket> handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onFrameReceived(Consumer<FrameData> handler) {
listeners.add(EventType.FRAMERECEIVED, handler);
}
@Override
public void offFrameReceived(Consumer<FrameData> handler) {
listeners.remove(EventType.FRAMERECEIVED, handler);
}
@Override
public void onFrameSent(Consumer<FrameData> handler) {
listeners.add(EventType.FRAMESENT, handler);
}
@Override
public void offFrameSent(Consumer<FrameData> handler) {
listeners.remove(EventType.FRAMESENT, handler);
}
@Override
public void onSocketError(Consumer<String> handler) {
listeners.add(EventType.SOCKETERROR, handler);
}
@Override
public void offSocketError(Consumer<String> handler) {
listeners.remove(EventType.SOCKETERROR, handler);
}
@Override
public FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options) {
if (options == null) {
options = new WaitForFrameReceivedOptions();
}
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.timeout);
}
@Override
public FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options) {
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
@@ -55,58 +117,36 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
return initializer.get("url").getAsString();
}
private class WaitableWebSocketError<R> implements Waitable<R>, Listener<EventType> {
private final List<EventType> subscribedEvents;
private String errorMessage;
WaitableWebSocketError() {
subscribedEvents = Arrays.asList(EventType.CLOSE, EventType.SOCKETERROR);
for (EventType e : subscribedEvents) {
addListener(e, this);
}
private class WaitableWebSocketClose<T> extends WaitableEvent<EventType, T> {
WaitableWebSocketClose() {
super(WebSocketImpl.this.listeners, EventType.CLOSE);
}
@Override
public void handle(Event<EventType> event) {
if (EventType.SOCKETERROR == event.type()) {
errorMessage = "Socket error";
} else if (EventType.CLOSE == event.type()) {
errorMessage = "Socket closed";
} else {
return;
}
dispose();
}
@Override
public boolean isDone() {
return errorMessage != null;
}
@Override
public R get() {
throw new PlaywrightException(errorMessage);
}
@Override
public void dispose() {
for (EventType e : subscribedEvents) {
removeListener(e, this);
}
public T get() {
throw new PlaywrightException("Socket closed");
}
}
@Override
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
if (options == null) {
options = new WaitForEventOptions();
private class WaitableWebSocketError<T> extends WaitableEvent<EventType, T> {
WaitableWebSocketError() {
super(WebSocketImpl.this.listeners, EventType.SOCKETERROR);
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
@Override
public T get() {
throw new PlaywrightException("Socket error");
}
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableWebSocketClose<>());
waitables.add(new WaitableWebSocketError<>());
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
waitables.add(page.createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
private static class FrameDataImpl implements FrameData {
@@ -153,7 +193,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
case "close": {
isClosed = true;
listeners.notify(EventType.CLOSE, null);
listeners.notify(EventType.CLOSE, this);
break;
}
default: {
@@ -21,6 +21,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
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;
@@ -28,39 +32,63 @@ class WorkerImpl extends ChannelOwner implements Worker {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
PageImpl page;
enum EventType {
CLOSE,
}
WorkerImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
public void onClose(Consumer<Worker> handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
public void offClose(Consumer<Worker> handler) {
listeners.remove(EventType.CLOSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Worker waitForClose(Runnable code, WaitForCloseOptions options) {
if (options == null) {
options = new WaitForCloseOptions();
}
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
}
@Override
public Object evaluate(String pageFunction, Object arg) {
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);
return deserialize(value);
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);
return deserialize(value);
});
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
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());
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());
});
}
@Override
@@ -68,11 +96,6 @@ class WorkerImpl extends ChannelOwner implements Worker {
return initializer.get("url").getAsString();
}
@Override
public Deferred<Event<EventType>> waitForEvent(EventType event) {
return toDeferred(new WaitableEvent<>(listeners, event));
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("close".equals(event)) {
@@ -114,7 +114,7 @@ public class Server implements HttpHandler {
}
}
Future<Request> waitForRequest(String path) {
Future<Request> futureRequest(String path) {
CompletableFuture<Request> future = requestSubscribers.get(path);
if (future == null) {
future = new CompletableFuture<>();
@@ -16,7 +16,10 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import java.io.IOException;
@@ -133,8 +136,10 @@ public class TestBase {
@AfterEach
void closeContext() {
context.close();
context = null;
page = null;
if (context != null) {
context.close();
context = null;
page = null;
}
}
}
@@ -16,35 +16,16 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowser {
private static Playwright playwright;
private Browser browser;
private boolean isChromium;
@BeforeAll
static void beforeAll() {
playwright = Playwright.create();
}
@BeforeEach
void setUp() {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
browser = playwright.chromium().launch(options);
isChromium = true;
}
@AfterEach
void tearDown() {
browser.close();
public class TestBrowser extends TestBase {
@Override
void createContextAndPage() {
// Do not create anything.
}
@Test
@@ -74,12 +55,15 @@ public class TestBrowser {
page.close();
}
@Test
void versionShouldWork() {
if (isChromium)
if (isChromium()) {
assertTrue(Pattern.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$", browser.version()));
else
} else if (isWebKit()) {
assertTrue(Pattern.matches("^\\d+\\.\\d+", browser.version()));
} else if (isFirefox()) {
// It can be 85.0b1 in Firefox.
assertTrue(Pattern.matches("^\\d+\\.\\d+.*", browser.version()));
}
}
}
@@ -64,7 +64,7 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldSendCookieHeader() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.waitForRequest("/empty.html");
Future<Server.Request> request = server.futureRequest("/empty.html");
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("cookie").withValue("value")));
Page page = context.newPage();
@@ -150,7 +150,7 @@ public class TestBrowserContextAddCookies extends TestBase {
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("sendcookie").withValue("value")));
{
Page page = context.newPage();
Future<Server.Request> request = server.waitForRequest("/empty.html");
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
List<String> cookies = request.get().headers.get("cookie");
assertEquals(asList("sendcookie=value"), cookies);
@@ -158,7 +158,7 @@ public class TestBrowserContextAddCookies extends TestBase {
{
BrowserContext context = browser.newContext();
Page page = context.newPage();
Future<Server.Request> request = server.waitForRequest("/empty.html");
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
List<String> cookies = request.get().headers.get("cookie");
assertNull(cookies);
@@ -0,0 +1,276 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.OutputStreamWriter;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextBasic extends TestBase {
@Test
void shouldCreateNewContext() {
assertEquals(1, browser.contexts().size());
BrowserContext context = browser.newContext();
assertEquals(2, browser.contexts().size());
assertTrue(browser.contexts().indexOf(context) != -1);
assertEquals(context.browser(), browser);
context.close();
assertEquals(1, browser.contexts().size());
assertEquals(context.browser(), browser);
}
@Test
void windowOpenShouldUseParentTabContext() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => window.open(url)", server.EMPTY_PAGE));
assertEquals(context, popup.context());
context.close();
}
@Test
void shouldIsolateLocalStorageAndCookies() {
// Create two incognito contexts.
BrowserContext context1 = browser.newContext();
BrowserContext context2 = browser.newContext();
assertEquals(0, context1.pages().size());
assertEquals(0, context2.pages().size());
// Create a page in first incognito context.
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
page1.evaluate("() => {\n" +
" localStorage.setItem('name', 'page1');\n" +
" document.cookie = 'name=page1';\n" +
"}");
assertEquals(1, context1.pages().size());
assertEquals(0, context2.pages().size());
// Create a page in second incognito context.
Page page2 = context2.newPage();
page2.navigate(server.EMPTY_PAGE);
page2.evaluate("() => {\n" +
" localStorage.setItem('name', 'page2');\n" +
" document.cookie = 'name=page2';\n" +
"}");
assertEquals(1, context1.pages().size());
assertEquals(1, context2.pages().size());
assertEquals(page1, context1.pages().get(0));
assertEquals(page2, context2.pages().get(0));
// Make sure pages don"t share localstorage or cookies.
assertEquals("page1", page1.evaluate("() => localStorage.getItem('name')"));
assertEquals("name=page1", page1.evaluate("() => document.cookie"));
assertEquals("page2", page2.evaluate("() => localStorage.getItem('name')"));
assertEquals("name=page2", page2.evaluate("() => document.cookie"));
// Cleanup contexts.
context1.close();
context2.close();
assertEquals(1, browser.contexts().size());
}
static void verifyViewport(Page page, int width, int height) {
assertEquals(width, page.viewportSize().width());
assertEquals(height, page.viewportSize().height());
assertEquals(width, page.evaluate("window.innerWidth"));
assertEquals(height, page.evaluate("window.innerHeight"));
}
@Test
void shouldPropagateDefaultViewportToThePage() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withViewport(456, 789));
Page page = context.newPage();
verifyViewport(page, 456, 789);
context.close();
}
void shouldMakeACopyOfDefaultViewport() {
// Doesn't make sense in Java.
}
@Test
void shouldRespectDeviceScaleFactor() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withDeviceScaleFactor(3.0));
Page page = context.newPage();
assertEquals(3, page.evaluate("window.devicePixelRatio"));
context.close();
}
@Test
@Disabled("TODO: supported null viewport option")
void shouldNotAllowDeviceScaleFactorWithNullViewport() {
try {
browser.newContext(new Browser.NewContextOptions().withDeviceScaleFactor(1.0));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
}
}
@Test
@Disabled("TODO: supported null viewport option")
void shouldNotAllowIsMobileWithNullViewport() {
try {
browser.newContext(new Browser.NewContextOptions().withIsMobile(true));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
}
}
@Test
void closeShouldWorkForEmptyContext() {
BrowserContext context = browser.newContext();
context.close();
}
@Test
void closeShouldAbortFutureEvent() {
BrowserContext context = browser.newContext();
try {
context.waitForPage(() -> context.close());
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Context closed"));
}
}
@Test
void closeShouldBeCallableTwice() {
BrowserContext context = browser.newContext();
context.close();
context.close();
context.close();
}
@Test
void shouldNotReportFramelessPagesOnError() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
server.setRoute("/empty.html", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<a href='" + server.EMPTY_PAGE + "' target='_blank'>Click me</a>");
}
});
Page[] popup = {null};
context.onPage(page1 -> popup[0] = page1);
page.navigate(server.EMPTY_PAGE);
page.click("'Click me'");
context.close();
if (popup[0] != null) {
// This races on Firefox :/
assertTrue(popup[0].isClosed());
assertNotNull(popup[0].mainFrame());
}
}
@Test
void shouldReturnAllOfThePages() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
Page second = context.newPage();
List<Page> allPages = context.pages();
assertEquals(2, allPages.size());
assertTrue(allPages.contains(page));
assertTrue(allPages.contains(second));
context.close();
}
@Test
void shouldCloseAllBelongingPagesOnceClosingContext() {
BrowserContext context = browser.newContext();
context.newPage();
assertEquals(1, context.pages().size());
context.close();
assertEquals(0, context.pages().size());
}
@Test
void shouldDisableJavascript() {
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withJavaScriptEnabled(false));
Page page = context.newPage();
page.navigate("data:text/html, <script>var something = 'forbidden'</script>");
try {
page.evaluate("something");
fail("did not throw");
} catch (PlaywrightException e) {
if (isWebKit())
assertTrue(e.getMessage().contains("Can\'t find variable: something"));
else
assertTrue(e.getMessage().contains("something is not defined"));
}
context.close();
}
{
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("data:text/html, <script>var something = 'forbidden'</script>");
assertEquals("forbidden", page.evaluate("something"));
context.close();
}
}
@Test
void shouldBeAbleToNavigateAfterDisablingJavascript() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withJavaScriptEnabled(false));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
context.close();
}
@Test
void shouldWorkWithOfflineOption() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withOffline(true));
Page page = context.newPage();
try {
page.navigate(server.EMPTY_PAGE);
fail("did not throw");
} catch (PlaywrightException e) {
}
context.setOffline(false);
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
context.close();
}
@Test
void shouldEmulateNavigatorOnLine() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
assertEquals(true, page.evaluate("() => window.navigator.onLine"));
context.setOffline(true);
assertEquals(false, page.evaluate("() => window.navigator.onLine"));
context.setOffline(false);
assertEquals(true, page.evaluate("() => window.navigator.onLine"));
context.close();
}
}
@@ -19,7 +19,6 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import static com.microsoft.playwright.Utils.getOS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static java.util.Arrays.asList;
@@ -95,11 +94,11 @@ public class TestBrowserContextRoute extends TestBase {
void shouldYieldToPageRoute() {
BrowserContext context = browser.newContext();
context.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("context"));
route.fulfill(new Route.FulfillOptions().withStatus(200).withBody("context"));
});
Page page = context.newPage();
page.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("page"));
route.fulfill(new Route.FulfillOptions().withStatus(200).withBody("page"));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertTrue(response.ok());
@@ -111,11 +110,11 @@ public class TestBrowserContextRoute extends TestBase {
void shouldFallBackToContextRoute() {
BrowserContext context = browser.newContext();
context.route("**/empty.html", route -> {
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("context"));
route.fulfill(new Route.FulfillOptions().withStatus(200).withBody("context"));
});
Page page = context.newPage();
page.route("**/non-empty.html", route -> {
route.fulfill(new Route.FulfillResponse().withStatus(200).withBody("page"));
route.fulfill(new Route.FulfillOptions().withStatus(200).withBody("page"));
});
Response response = page.navigate(server.EMPTY_PAGE);
assertTrue(response.ok());
@@ -35,7 +35,7 @@ public class TestBrowserContextStorageState extends TestBase {
@Test
void shouldCaptureLocalStorage() {
page.route("**/*", route -> {
route.fulfill(new Route.FulfillResponse().withBody("<html></html>"));
route.fulfill(new Route.FulfillOptions().withBody("<html></html>"));
});
page.navigate("https://www.example.com");
page.evaluate("localStorage['name1'] = 'value1';");
@@ -67,7 +67,7 @@ public class TestBrowserContextStorageState extends TestBase {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withStorageState(storageState));
Page page = context.newPage();
page.route("**/*", route -> {
route.fulfill(new Route.FulfillResponse().withBody("<html></html>"));
route.fulfill(new Route.FulfillOptions().withBody("<html></html>"));
});
page.navigate("https://www.example.com");
Object localStorage = page.evaluate("window.localStorage");
@@ -79,7 +79,7 @@ public class TestBrowserContextStorageState extends TestBase {
void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException {
Page page1 = context.newPage();
page1.route("**/*", route -> {
route.fulfill(new Route.FulfillResponse().withBody("<html></html>"));
route.fulfill(new Route.FulfillOptions().withBody("<html></html>"));
});
page1.navigate("https://www.example.com");
page1.evaluate("() => {\n" +
@@ -118,7 +118,7 @@ public class TestBrowserContextStorageState extends TestBase {
BrowserContext context2 = browser.newContext(new Browser.NewContextOptions().withStorageState(path));
Page page2 = context2.newPage();
page2.route("**/*", route -> {
route.fulfill(new Route.FulfillResponse().withBody("<html></html>"));
route.fulfill(new Route.FulfillOptions().withBody("<html></html>"));
});
page2.navigate("https://www.example.com");
Object localStorage = page2.evaluate("window.localStorage");
@@ -26,7 +26,6 @@ import java.util.List;
import static com.microsoft.playwright.Keyboard.Modifier.SHIFT;
import static com.microsoft.playwright.Mouse.Button.RIGHT;
import static com.microsoft.playwright.Page.EventType.CONSOLE;
import static org.junit.jupiter.api.Assertions.*;
public class TestClick extends TestBase {
@@ -104,9 +103,7 @@ public class TestClick extends TestBase {
Page page = context.newPage();
page.navigate(server.PREFIX + "/wrappedlink.html");
Deferred<Response> navigationPromise = page.waitForNavigation();
page.click("a");
navigationPromise.get();
page.waitForNavigation(() -> page.click("a"));
assertEquals(server.PREFIX + "/wrappedlink.html#clicked", page.url());
context.close();
@@ -142,7 +139,7 @@ public class TestClick extends TestBase {
void shouldClickOffscreenButtons() {
page.navigate(server.PREFIX + "/offscreenbuttons.html");
List<String> messages = new ArrayList<>();
page.addListener(CONSOLE, event -> messages.add(((ConsoleMessage) event.data()).text()));
page.onConsole(message -> messages.add(message.text()));
for (int i = 0; i < 11; ++i) {
// We might've scrolled to click a button - reset to (0, 0).
page.evaluate("() => window.scrollTo(0, 0)");
@@ -344,7 +341,7 @@ public class TestClick extends TestBase {
void shouldClickTheButtonWithDeviceScaleFactorSet() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withViewport(400, 400)
.withDeviceScaleFactor(5));
.withDeviceScaleFactor(5.0));
Page page = context.newPage();
assertEquals(5, page.evaluate("() => window.devicePixelRatio"));
page.setContent("<div style='width:100px;height:100px'>spacer</div>");
@@ -511,6 +508,13 @@ public class TestClick extends TestBase {
assertEquals(true, page.evaluate("__CLICKED"));
}
@Test
void shouldWorkWithUnicodeSelectors() {
page.setContent("<button onclick='javascript:window.__CLICKED=true;'><label style='pointer-events:none'>Найти</label></button>");
page.click("text=Найти");
assertEquals(true, page.evaluate("__CLICKED"));
}
@Test
void shouldClimbUpTo_roleButton_() {
page.setContent("<div role=button onclick='javascript:window.__CLICKED=true;'><div style='pointer-events:none'><span><div>Click target</div></span></div>");
@@ -109,7 +109,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
void shouldSupportExtraHTTPHeadersOption() throws ExecutionException, InterruptedException {
// TODO: test.flaky(browserName === "firefox" && headful && platform === "linux", "Intermittent timeout on bots");
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().withExtraHTTPHeaders(mapOf("foo", "bar")));
Future<Server.Request> request = server.waitForRequest("/empty.html");
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
assertEquals(asList("bar"), request.get().headers.get("foo"));
}
@@ -199,6 +199,17 @@ public class TestDefaultBrowserContext2 extends TestBase {
}
}
@Test
void shouldWorkWithIgnoreDefaultArgs() {
// Ignore arguments by name.
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().withIgnoreDefaultArgs(asList("foo"));
Browser browser = browserType.launch(options);
Page page = browser.newPage();
browser.close();
// Check that there is a way to ignore all arguments.
new BrowserType.LaunchOptions().withIgnoreAllDefaultArgs(true);
}
void shouldHavePassedURLWhenLaunchingWithIgnoreDefaultArgsTrue() {
}
@@ -212,7 +223,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
void shouldFireCloseEventForAPersistentContext() {
launchPersistent();
boolean[] closed = {false};
persistentContext.addListener(BrowserContext.EventType.CLOSE, event -> closed[0] = true);
persistentContext.onClose(context -> closed[0] = true);
closePersistentContext();
assertTrue(closed[0]);
}
@@ -18,11 +18,9 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import static com.microsoft.playwright.Dialog.Type.ALERT;
import static com.microsoft.playwright.Dialog.Type.PROMPT;
import static com.microsoft.playwright.Page.EventType.DIALOG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -30,8 +28,7 @@ public class TestDialog extends TestBase {
@Test
void shouldFire() {
page.addListener(DIALOG, event -> {
Dialog dialog = (Dialog) event.data();
page.onDialog(dialog -> {
assertEquals(ALERT, dialog.type());
assertEquals( "", dialog.defaultValue());
assertEquals( "yo", dialog.message());
@@ -42,8 +39,7 @@ public class TestDialog extends TestBase {
@Test
void shouldAllowAcceptingPrompts() {
page.addListener(DIALOG, event -> {
Dialog dialog = (Dialog) event.data();
page.onDialog(dialog -> {
assertEquals(PROMPT, dialog.type());
assertEquals("yes.", dialog.defaultValue());
assertEquals("question?", dialog.message());
@@ -55,8 +51,7 @@ public class TestDialog extends TestBase {
@Test
void shouldDismissThePrompt() {
page.addListener(DIALOG, event -> {
Dialog dialog = (Dialog) event.data();
page.onDialog(dialog -> {
dialog.dismiss();
});
Object result = page.evaluate("() => prompt('question?')");
@@ -65,8 +60,7 @@ public class TestDialog extends TestBase {
@Test
void shouldAcceptTheConfirmPrompt() {
page.addListener(DIALOG, event -> {
Dialog dialog = (Dialog) event.data();
page.onDialog(dialog -> {
dialog.accept();
});
Object result = page.evaluate("() => confirm('boolean?')");
@@ -75,8 +69,7 @@ public class TestDialog extends TestBase {
@Test
void shouldDismissTheConfirmPrompt() {
page.addListener(DIALOG, event -> {
Dialog dialog = (Dialog) event.data();
page.onDialog(dialog -> {
dialog.dismiss();
});
Object result = page.evaluate("() => confirm('boolean?')");
@@ -92,7 +85,7 @@ public class TestDialog extends TestBase {
void shouldBeAbleToCloseContextWithOpenAlert() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
// const alertPromise = page.waitForEvent("dialog");
// const alertPromise = page.futureEvent("dialog");
page.evaluate("() => {\n" +
" setTimeout(() => alert('hello'), 0);\n" +
"}");
@@ -19,7 +19,8 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class TestDispatchEvent extends TestBase {
@@ -21,8 +21,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -30,7 +32,6 @@ import java.time.Duration;
import java.time.Instant;
import static com.microsoft.playwright.Keyboard.Modifier.ALT;
import static com.microsoft.playwright.Page.EventType.DOWNLOAD;
import static com.microsoft.playwright.Utils.copy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
@@ -61,9 +62,7 @@ public class TestDownload extends TestBase {
@Test
void shouldReportDownloadsWithAcceptDownloadsFalse() {
page.setContent("<a href='" + server.PREFIX + "/downloadWithFilename'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals(server.PREFIX + "/downloadWithFilename", download.url());
assertEquals("file.txt", download.suggestedFilename());
try {
@@ -78,9 +77,7 @@ public class TestDownload extends TestBase {
void shouldReportDownloadsWithAcceptDownloadsTrue() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -92,9 +89,7 @@ public class TestDownload extends TestBase {
void shouldSaveToUserSpecifiedPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -108,9 +103,7 @@ public class TestDownload extends TestBase {
void shouldSaveToUserSpecifiedPathWithoutUpdatingOriginalPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -133,9 +126,7 @@ public class TestDownload extends TestBase {
void shouldSaveToTwoDifferentPathsWithMultipleSaveAsCalls() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
{
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -157,9 +148,7 @@ public class TestDownload extends TestBase {
void shouldSaveToOverwrittenFilepath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
{
download.saveAs(userFile);
@@ -180,9 +169,7 @@ public class TestDownload extends TestBase {
void shouldCreateSubdirectoriesWhenSavingToNonExistentUserSpecifiedPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path downloads = Files.createTempDirectory("downloads");
Path nestedPath = downloads.resolve(Paths.get("these", "are", "directories", "download.txt"));
@@ -201,9 +188,7 @@ public class TestDownload extends TestBase {
void shouldErrorWhenSavingWithDownloadsDisabled() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(false));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = Files.createTempFile("download-", ".txt");
try {
@@ -219,9 +204,7 @@ public class TestDownload extends TestBase {
void shouldErrorWhenSavingAfterDeletion() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = Files.createTempFile("download-", ".txt");
download.delete();
@@ -252,9 +235,7 @@ public class TestDownload extends TestBase {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<a download='file.txt' href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals("file.txt", download.suggestedFilename());
Path path = download.path();
@@ -269,16 +250,15 @@ public class TestDownload extends TestBase {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
@SuppressWarnings("unchecked")
Event<Page.EventType>[] event = new Event[]{null};
page.addListener(DOWNLOAD, e -> event[0] = e);
Download[] download = {null};
page.onDownload(d -> download[0] = d);
page.click("a");
Instant start = Instant.now();
while (event[0] == null) {
page.waitForTimeout(100).get();
while (download[0] == null) {
page.waitForTimeout(100);
assertTrue(Duration.between(start, Instant.now()).getSeconds() < 30, "Timed out");
}
Download download = (Download) event[0].data();
Path path = download.path();
Path path = download[0].path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
assertEquals("Hello world", new String(bytes, UTF_8));
@@ -289,17 +269,16 @@ public class TestDownload extends TestBase {
void shouldReportDownloadPathWithinPageOnDownloadHandlerForBlobs() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
@SuppressWarnings("unchecked")
Event<Page.EventType>[] event = new Event[]{null};
page.addListener(DOWNLOAD, e -> event[0] = e);
Download[] download = {null};
page.onDownload(d -> download[0] = d);
page.navigate(server.PREFIX + "/download-blob.html");
page.click("a");
Instant start = Instant.now();
while (event[0] == null) {
page.waitForTimeout(100).get();
while (download[0] == null) {
page.waitForTimeout(100);
assertTrue(Duration.between(start, Instant.now()).getSeconds() < 1, "Timed out");
}
Download download = (Download) event[0].data();
Path path = download.path();
Path path = download[0].path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
assertEquals("Hello world", new String(bytes, UTF_8));
@@ -320,9 +299,7 @@ public class TestDownload extends TestBase {
});
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a", new Page.ClickOptions().withModifiers(ALT));
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a", new Page.ClickOptions().withModifiers(ALT)));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -343,9 +320,7 @@ public class TestDownload extends TestBase {
// - WebKit doesn't close the popup page
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a target=_blank href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -357,9 +332,7 @@ public class TestDownload extends TestBase {
void shouldDeleteFile() {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
download.delete();
@@ -371,9 +344,7 @@ public class TestDownload extends TestBase {
void shouldExposeStream() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
InputStream stream = download.createReadStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -386,14 +357,8 @@ public class TestDownload extends TestBase {
void shouldDeleteDownloadsOnContextDestruction() {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent1 = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download1 = (Download) downloadEvent1.get().data();
Deferred<Event<Page.EventType>> downloadEvent2 = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download2 = (Download) downloadEvent2.get().data();
Download download1 = page.waitForDownload(() -> page.click("a"));
Download download2 = page.waitForDownload(() -> page.click("a"));
Path path1 = download1.path();
Path path2 = download2.path();
assertTrue(Files.exists(path1));
@@ -408,14 +373,8 @@ public class TestDownload extends TestBase {
Browser browser = browserType.launch();
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent1 = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download1 = (Download) downloadEvent1.get().data();
Deferred<Event<Page.EventType>> downloadEvent2 = page.waitForEvent(DOWNLOAD);
page.click("a");
Download download2 = (Download) downloadEvent2.get().data();
Download download1 = page.waitForDownload(() -> page.click("a"));
Download download2 = page.waitForDownload(() -> page.click("a"));
Path path1 = download1.path();
Path path2 = download2.path();
assertTrue(Files.exists(path1));
@@ -0,0 +1,250 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleConvenience extends TestBase {
@Test
void shouldHaveANicePreview() {
page.navigate(server.PREFIX + "/dom.html");
ElementHandle outer = page.querySelector("#outer");
ElementHandle inner = page.querySelector("#inner");
ElementHandle check = page.querySelector("#check");
JSHandle text = inner.evaluateHandle("e => e.firstChild");
page.evaluate("() => 1"); // Give them a chance to calculate the preview.
assertEquals("JSHandle@<div id=\"outer\" name=\"value\">…</div>", outer.toString());
assertEquals("JSHandle@<div id=\"inner\">Text,↵more text</div>", inner.toString());
assertEquals("JSHandle@#text=Text,↵more text", text.toString());
assertEquals("JSHandle@<input checked id=\"check\" foo=\"bar\"\" type=\"checkbox\"/>", check.toString());
}
@Test
void getAttributeShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
ElementHandle handle = page.querySelector("#outer");
assertEquals("value", handle.getAttribute("name"));
assertNull(handle.getAttribute("foo"));
assertEquals("value", page.getAttribute("#outer", "name"));
assertNull(page.getAttribute("#outer", "foo"));
}
@Test
void innerHTMLShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
ElementHandle handle = page.querySelector("#outer");
assertEquals("<div id=\"inner\">Text,\nmore text</div>", handle.innerHTML());
assertEquals("<div id=\"inner\">Text,\nmore text</div>", page.innerHTML("#outer"));
}
@Test
void innerTextShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
ElementHandle handle = page.querySelector("#inner");
assertEquals("Text, more text", handle.innerText());
assertEquals("Text, more text", page.innerText("#inner"));
}
@Test
void innerTextShouldThrow() {
page.setContent("<svg>text</svg>");
try {
page.innerText("svg");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not an HTMLElement"));
}
ElementHandle handle = page.querySelector("svg");
try {
handle.innerText();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not an HTMLElement"));
}
}
@Test
void textContentShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
ElementHandle handle = page.querySelector("#inner");
assertEquals("Text,\nmore text", handle.textContent());
assertEquals("Text,\nmore text", page.textContent("#inner"));
}
@Test
void textContentShouldBeAtomic() {
String createDummySelector = "{\n" +
" query(root, selector) {\n" +
" const result = root.querySelector(selector);\n" +
" if (result)\n" +
" Promise.resolve().then(() => result.textContent = 'modified');\n" +
" return result;\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" const result = Array.from(root.querySelectorAll(selector));\n" +
" for (const e of result)\n" +
" Promise.resolve().then(() => e.textContent = 'modified');\n" +
" return result;\n" +
" }\n" +
"}\n";
playwright.selectors().register("textContent", createDummySelector);
page.setContent("<div>Hello</div>");
String tc = page.textContent("textContent=div");
assertEquals("Hello", tc);
assertEquals("modified", page.evaluate("() => document.querySelector('div').textContent"));
}
@Test
void innerTextShouldBeAtomic() {
String createDummySelector = "{\n" +
" query(root, selector) {\n" +
" const result = root.querySelector(selector);\n" +
" if (result)\n" +
" Promise.resolve().then(() => result.textContent = 'modified');\n" +
" return result;\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" const result = Array.from(root.querySelectorAll(selector));\n" +
" for (const e of result)\n" +
" Promise.resolve().then(() => e.textContent = 'modified');\n" +
" return result;\n" +
" }\n" +
"}\n";
playwright.selectors().register("innerText", createDummySelector);
page.setContent("<div>Hello</div>");
String tc = page.innerText("innerText=div");
assertEquals("Hello", tc);
assertEquals("modified", page.evaluate("() => document.querySelector('div').innerText"));
}
@Test
void innerHTMLShouldBeAtomic() {
String createDummySelector = "{\n" +
" query(root, selector) {\n" +
" const result = root.querySelector(selector);\n" +
" if (result)\n" +
" Promise.resolve().then(() => result.textContent = 'modified');\n" +
" return result;\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" const result = Array.from(root.querySelectorAll(selector));\n" +
" for (const e of result)\n" +
" Promise.resolve().then(() => e.textContent = 'modified');\n" +
" return result;\n" +
" }\n" +
"}\n";
playwright.selectors().register("innerHTML", createDummySelector);
page.setContent("<div>Hello<span>world</span></div>");
String tc = page.innerHTML("innerHTML=div");
assertEquals("Hello<span>world</span>", tc);
assertEquals("modified", page.evaluate("() => document.querySelector('div').innerHTML"));
}
@Test
void getAttributeShouldBeAtomic() {
String createDummySelector = "{\n" +
" query(root, selector) {\n" +
" const result = root.querySelector(selector);\n" +
" if (result)\n" +
" Promise.resolve().then(() => result.setAttribute('foo', 'modified'));\n" +
" return result;\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" const result = Array.from(root.querySelectorAll(selector));\n" +
" for (const e of result)\n" +
" Promise.resolve().then(() => e.setAttribute('foo', 'modified'));\n" +
" return result;\n" +
" }\n" +
"}\n";
playwright.selectors().register("getAttribute", createDummySelector);
page.setContent("<div foo=hello></div>");
String tc = page.getAttribute("getAttribute=div", "foo");
assertEquals("hello", tc);
assertEquals("modified", page.evaluate("() => document.querySelector('div').getAttribute('foo')"));
}
@Test
void isVisibleAndIsHiddenShouldWork() {
page.setContent("<div>Hi</div><span></span>");
ElementHandle div = page.querySelector("div");
assertTrue(div.isVisible());
assertFalse(div.isHidden());
assertTrue(page.isVisible("div"));
assertFalse(page.isHidden("div"));
ElementHandle span = page.querySelector("span");
assertFalse(span.isVisible());
assertTrue(span.isHidden());
assertFalse(page.isVisible("span"));
assertTrue(page.isHidden("span"));
}
@Test
void isEnabledAndIsDisabledShouldWork() {
page.setContent(" <button disabled>button1</button>\n" +
"<button>button2</button>\n" +
"<div>div</div>");
ElementHandle div = page.querySelector("div");
assertTrue(div.isEnabled());
assertFalse(div.isDisabled());
assertTrue(page.isEnabled("div"));
assertFalse(page.isDisabled("div"));
ElementHandle button1 = page.querySelector(":text('button1')");
assertFalse(button1.isEnabled());
assertTrue(button1.isDisabled());
assertFalse(page.isEnabled(":text('button1')"));
assertTrue(page.isDisabled(":text('button1')"));
ElementHandle button2 = page.querySelector(":text('button2')");
assertTrue(button2.isEnabled());
assertFalse(button2.isDisabled());
assertTrue(page.isEnabled(":text('button2')"));
assertFalse(page.isDisabled(":text('button2')"));
}
@Test
void isEditableShouldWork() {
page.setContent("<input id=input1 disabled><textarea></textarea><input id=input2>");
page.evalOnSelector("textarea", "t => t.readOnly = true");
ElementHandle input1 = page.querySelector("#input1");
assertFalse(input1.isEditable());
assertFalse(page.isEditable("#input1"));
ElementHandle input2 = page.querySelector("#input2");
assertTrue(input2.isEditable());
assertTrue(page.isEditable("#input2"));
ElementHandle textarea = page.querySelector("textarea");
assertFalse(textarea.isEditable());
assertFalse(page.isEditable("textarea"));
}
@Test
void isCheckedShouldWork() {
page.setContent("<input type='checkbox' checked><div>Not a checkbox</div>");
ElementHandle handle = page.querySelector("input");
assertTrue(handle.isChecked());
assertTrue(page.isChecked("input"));
handle.evaluate("input => input.checked = false");
assertFalse(handle.isChecked());
assertFalse(page.isChecked("input"));
try {
page.isChecked("div");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Not a checkbox or radio button"));
}
}
}
@@ -96,16 +96,15 @@ public class TestElementHandleOwnerFrame extends TestBase {
@Test
void shouldWorkForAdoptedElements() {
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.waitForEvent(Page.EventType.POPUP);
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
JSHandle divHandle = page.evaluateHandle("() => {\n" +
" const div = document.createElement('div');\n" +
" document.body.appendChild(div);\n" +
" return div;\n" +
"}");
assertEquals(page.mainFrame(), divHandle.asElement().ownerFrame());
Page popup = (Page) popupEvent.get().data();
popup.waitForLoadState(Page.LoadState.DOMCONTENTLOADED).get();
popup.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
page.evaluate("() => {\n" +
" const div = document.querySelector('div');\n" +
" window['__popup'].document.body.appendChild(div);\n" +
@@ -0,0 +1,53 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestElementHandlePress extends TestBase {
@Test
void shouldWork() {
page.setContent("<input type='text' />");
page.press("input", "h");
assertEquals("h", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldNotSelectExistingValue() {
page.setContent("<input type='text' value='hello' />");
page.press("input", "w");
assertEquals("whello", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldResetSelectionWhenNotFocused() {
page.setContent("<input type='text' value='hello' /><div tabIndex=2>text</div>");
page.evalOnSelector("input", "input => {\n" +
" input.selectionStart = 2;\n" +
" input.selectionEnd = 4;\n" +
" document.querySelector('div').focus();\n" +
" }");
page.press("input", "w");
assertEquals("whello", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldNotModifySelectionWhenFocused() {
page.setContent("<input type='text' value='hello' />");
page.evalOnSelector("input", "input => {\n" +
" input.focus();\n" +
" input.selectionStart = 2;\n" +
" input.selectionEnd = 4;\n" +
" }");
page.press("input", "w");
assertEquals("hewo", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldWorkWithNumberInput() {
page.setContent("<input type='number' value=2 />");
page.press("input", "1");
assertEquals("12", page.evalOnSelector("input", "input => input.value"));
}
}
@@ -0,0 +1,96 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleQuerySelector extends TestBase {
@Test
void shouldQueryExistingElement() {
page.navigate(server.PREFIX + "/playground.html");
page.setContent("<html><body><div class=\"second\"><div class=\"inner\">A</div></div></body></html>");
ElementHandle html = page.querySelector("html");
ElementHandle second = html.querySelector(".second");
ElementHandle inner = second.querySelector(".inner");
String content = (String) page.evaluate("e => e.textContent", inner);
assertEquals( "A", content);
}
@Test
void shouldReturnNullForNonExistingElement() {
page.setContent("<html><body><div class=\"second\"><div class=\"inner\">B</div></div></body></html>");
ElementHandle html = page.querySelector("html");
ElementHandle second = html.querySelector(".third");
assertNull(second);
}
@Test
void shouldWorkForAdoptedElements() {
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
// Test JSHandle
JSHandle divHandle = page.evaluateHandle("() => {\n" +
" const div = document.createElement('div');\n" +
" document.body.appendChild(div);\n" +
" const span = document.createElement('span');\n" +
" span.textContent = 'hello';\n" +
" div.appendChild(span);\n" +
" return div;" +
"}");
assertNotNull(divHandle.asElement().querySelector("span"));
assertEquals("hello", divHandle.asElement().querySelector("span").evaluate( "e => e.textContent"));
// Test Popup
popup.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
page.evaluate("() => {\n" +
" const div = document.querySelector('div');\n" +
" window['__popup'].document.body.appendChild(div);\n" +
" }");
assertNotNull(divHandle.asElement().querySelector("span"));
assertEquals("hello", divHandle.asElement().querySelector("span").evaluate( "e => e.textContent"));
assertNotNull(popup.querySelector("span"));
assertEquals("hello", popup.querySelector("span").evaluate("e => e.textContent"));
}
@Test
void shouldQueryExistingElements() {
page.setContent("<html><body><div>A</div><br/><div>B</div></body></html>");
ElementHandle html = page.querySelector("html");
List<ElementHandle> elements = html.querySelectorAll("div");
assertEquals(2, elements.size());
List<String> result = new ArrayList<>();
elements.stream().forEach(element -> result.add((String) page.evaluate("e => e.textContent", element)));
assertTrue(Arrays.asList("A", "B").equals(result));
}
@Test
void shouldReturnEmptyArrayForNonExistingElement() {
page.setContent("<html><body><span>A</span><br/><span>B</span></body></html>");
ElementHandle html = page.querySelector("html");
List<ElementHandle> elements = html.querySelectorAll("div");
assertEquals(0, elements.size());
}
@Test
void xpathShouldQueryExistingElement() {
page.navigate(server.PREFIX + "/playground.html");
page.setContent("<html><body><div class=\"second\"><div class=\"inner\">A</div></div></body></html>");
ElementHandle html = page.querySelector("html");
List<ElementHandle> second = html.querySelectorAll("xpath=./body/div[contains(@class, 'second')]");
List<ElementHandle> inner = second.get(0).querySelectorAll("xpath=./div[contains(@class, 'inner')]");
String content = (String) page.evaluate("e => e.textContent", inner.get(0));
assertEquals("A", content);
}
@Test
void xpathShouldReturnNullForNonExistingElement() {
page.setContent("<html><body><div class=\"second\"><div class=\"inner\">B</div></div></body></html>");
ElementHandle html = page.querySelector("html");
List<ElementHandle> second = html.querySelectorAll("xpath=/div[contains(@class, 'third')]");
assertTrue(second.isEmpty());
}
}
@@ -0,0 +1,62 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleSelectText extends TestBase {
@Test
void shouldSelectTextarea() {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle textarea = page.querySelector("textarea");
textarea.evaluate("textarea => textarea.value = 'some value'");
textarea.selectText();
if (isFirefox()) {
assertEquals(0, textarea.evaluate("el => el.selectionStart"));
assertEquals(10, textarea.evaluate("el => el.selectionEnd"));
} else {
assertEquals("some value", page.evaluate("() => window.getSelection().toString()"));
}
}
@Test
void shouldSelectInput() {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle input = page.querySelector("input");
input.evaluate("input => input.value = 'some value'");
input.selectText();
if (isFirefox()) {
assertEquals(0, input.evaluate("el => el.selectionStart"));
assertEquals(10, input.evaluate("el => el.selectionEnd"));
} else {
assertEquals("some value", page.evaluate("() => window.getSelection().toString()"));
}
}
@Test
void shouldSelectPlainDiv() {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle div = page.querySelector("div.plain");
div.selectText();
assertEquals("Plain div", page.evaluate("() => window.getSelection().toString()"));
}
@Test
void shouldTimeoutWaitingForInvisibleElement() {
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle textarea = page.querySelector("textarea");
textarea.evaluate("e => e.style.display = 'none'");
try {
textarea.selectText(new ElementHandle.SelectTextOptions().withTimeout(3000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("element is not visible"));
}
}
// @Test
void shouldWaitForVisible() {
// TODO Wait for Async API implementation
}
}
@@ -0,0 +1,53 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestElementHandleType extends TestBase {
@Test
void shouldWork() {
page.setContent("<input type='text' />");
page.type("input", "hello");
assertEquals("hello", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldNotSelectExistingValue() {
page.setContent("<input type='text' value='hello' />");
page.type("input", "world");
assertEquals("worldhello", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldResetSelectionWhenNotFocus() {
page.setContent("<input type='text' value='hello' /><div tabIndex=2>text</div>");
page.evalOnSelector("input", "input => {\n" +
" input.selectionStart = 2;\n" +
" input.selectionEnd = 4;\n" +
" document.querySelector('div').focus();\n" +
" }");
page.type("input", "world");
assertEquals("worldhello", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldNotModifySelectionWhenFocus() {
page.setContent("<input type='text' value='hello' />");
page.evalOnSelector("input", "input => {\n" +
" input.focus();\n" +
" input.selectionStart = 2;\n" +
" input.selectionEnd = 4;\n" +
" }");
page.type("input", "world");
assertEquals("heworldo", page.evalOnSelector("input", "input => input.value"));
}
@Test
void shouldWorkWithNumberInput() {
page.setContent("<input type='number' value=2 />");
page.type("input", "13");
assertEquals("132", page.evalOnSelector("input", "input => input.value"));
}
}
@@ -35,10 +35,9 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldWaitForVisible() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(VISIBLE);
giveItAChanceToResolve(page);
div.evaluate("div => div.style.display = 'block'");
promise.get();
div.waitForElementState(VISIBLE);
}
@Test
@@ -52,9 +51,8 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldTimeoutWaitingForVisible() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> result = div.waitForElementState(VISIBLE, new ElementHandle.WaitForElementStateOptions().withTimeout(1000));
try {
result.get();
div.waitForElementState(VISIBLE, new ElementHandle.WaitForElementStateOptions().withTimeout(1000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout 1000ms exceeded"));
@@ -65,10 +63,9 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldThrowWaitingForVisibleWhenDetached() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(VISIBLE);
div.evaluate("div => div.remove()");
try {
promise.get();
div.waitForElementState(VISIBLE);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
@@ -79,48 +76,43 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldWaitForHidden() {
page.setContent("<div>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(HIDDEN);
giveItAChanceToResolve(page);
div.evaluate("div => div.style.display = 'none'");
promise.get();
div.waitForElementState(HIDDEN);
}
@Test
void shouldWaitForAlreadyHidden() {
page.setContent("<div></div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> result = div.waitForElementState(HIDDEN);
result.get();
div.waitForElementState(HIDDEN);
}
@Test
void shouldWaitForHiddenWhenDetached() {
page.setContent("<div>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(HIDDEN);
giveItAChanceToResolve(page);
div.evaluate("div => div.remove()");
promise.get();
div.waitForElementState(HIDDEN);
}
@Test
void shouldWaitForEnabledButton() {
page.setContent("<button disabled><span>Target</span></button>");
ElementHandle span = page.querySelector("text=Target");
Deferred<Void> promise = span.waitForElementState(ENABLED);
giveItAChanceToResolve(page);
span.evaluate("span => span.parentElement.disabled = false");
promise.get();
span.waitForElementState(ENABLED);
}
@Test
void shouldThrowWaitingForEnabledWhenDetached() {
page.setContent("<button disabled>Target</button>");
ElementHandle button = page.querySelector("button");
Deferred<Void> promise = button.waitForElementState(ENABLED);
button.evaluate("button => button.remove()");
try {
promise.get();
button.waitForElementState(ENABLED);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
@@ -131,10 +123,9 @@ public class TestElementHandleWaitForElementState extends TestBase {
void shouldWaitForDisabledButton() {
page.setContent("<button><span>Target</span></button>");
ElementHandle span = page.querySelector("text=Target");
Deferred<Void> promise = span.waitForElementState(DISABLED);
giveItAChanceToResolve(page);
span.evaluate("span => span.parentElement.disabled = true");
promise.get();
span.waitForElementState(DISABLED);
}
static boolean isFirefoxLinux() {
@@ -149,9 +140,8 @@ public class TestElementHandleWaitForElementState extends TestBase {
" button.style.transition = 'margin 10000ms linear 0s';\n" +
" button.style.marginLeft = '20000px';\n" +
"}");
Deferred<Void> promise = button.waitForElementState(STABLE);
giveItAChanceToResolve(page);
button.evaluate("button => button.style.transition = ''");
promise.get();
button.waitForElementState(STABLE);
}
}
@@ -224,18 +224,16 @@ public class TestEvalOnSelector extends TestBase {
@Test
void shouldWorkWithSpacesInCssAttributesWhenMissing() {
Deferred<ElementHandle> inputPromise = page.waitForSelector("[placeholder='Select date']");
assertNull(page.querySelector("[placeholder='Select date']"));
page.setContent("<div><input placeholder='Select date'></div>");
inputPromise.get();
page.waitForSelector("[placeholder='Select date']");
}
@Test
void shouldWorkWithQuotesInCssAttributesWhenMissing() {
Deferred<ElementHandle> inputPromise = page.waitForSelector("[placeholder='Select\\\"date']");
assertNull(page.querySelector("[placeholder='Select\\\"date']"));
page.setContent("<div><input placeholder='Select&quot;date'></div>");
inputPromise.get();
page.waitForSelector("[placeholder='Select\\\"date']");
}
@Test
@@ -21,8 +21,6 @@ import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.Page.EventType.CONSOLE;
import static com.microsoft.playwright.Page.EventType.POPUP;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -111,7 +109,7 @@ public class TestGeolocation extends TestBase {
context.grantPermissions(asList("geolocation"));
page.navigate(server.EMPTY_PAGE);
List<String> messages = new ArrayList<>();
page.addListener(CONSOLE, event -> messages.add(((ConsoleMessage) event.data()).text()));
page.onConsole(message -> messages.add(message.text()));
context.setGeolocation(new Geolocation());
page.evaluate("() => {\n" +
" navigator.geolocation.watchPosition(pos => {\n" +
@@ -120,19 +118,26 @@ public class TestGeolocation extends TestBase {
" }, err => {});\n" +
"}");
{
Deferred<Event<Page.EventType>> deferred = page.waitForEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=0 lng=10"));
context.setGeolocation(new Geolocation(0, 10));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(0, 10)));
// Location change events come several times so we loop until expected one is received.
while (!message.text().contains("lat=0 lng=10")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=0 lng=10"), message.text());
}
{
Deferred<Event<Page.EventType>> deferred = page.waitForEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=20 lng=30"));
context.setGeolocation(new Geolocation(20, 30));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(20, 30)));
while (!message.text().contains("lat=20 lng=30")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=20 lng=30"), message.text());
}
{
Deferred<Event<Page.EventType>> deferred = page.waitForEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=40 lng=50"));
context.setGeolocation(new Geolocation(40, 50));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(40, 50)));
while (!message.text().contains("lat=40 lng=50")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=40 lng=50"), message.text());
}
assertTrue(messages.contains("lat=0 lng=10"));
assertTrue(messages.contains("lat=20 lng=30"));
@@ -143,9 +148,8 @@ public class TestGeolocation extends TestBase {
void shouldUseContextOptionsForPopup() {
context.grantPermissions(asList("geolocation"));
context.setGeolocation(new Geolocation(10, 10));
Deferred<Event<Page.EventType>> popupEvent = page.waitForEvent(POPUP);
page.evaluate("url => window['_popup'] = window.open(url)", server.PREFIX + "/geolocation.html");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window['_popup'] = window.open(url)", server.PREFIX + "/geolocation.html"));
popup.waitForLoadState();
Object geolocation = popup.evaluate("window['geolocationPromise']");
assertEquals(mapOf("longitude", 10, "latitude", 10), geolocation);
@@ -19,12 +19,10 @@ package com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
@@ -101,8 +99,7 @@ public class TestHar extends TestBase {
void shouldHavePages() throws IOException {
pageWithHar.page.navigate("data:text/html,<title>Hello</title>");
// For data: load comes before domcontentloaded...
Deferred<Void> loadEvent = pageWithHar.page.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
loadEvent.get();
pageWithHar.page.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
JsonObject log = pageWithHar.log();
assertEquals(1, log.getAsJsonArray("pages").size());
@@ -125,8 +122,7 @@ public class TestHar extends TestBase {
page.navigate("data:text/html,<title>Hello</title>");
// For data: load comes before domcontentloaded...
Deferred<Void> loadEvent = page.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
loadEvent.get();
page.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
context.close();
JsonObject log;
try (Reader reader = new FileReader(harPath.toFile())) {

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