Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1432b62b84 | |||
| fd87e552f2 | |||
| 0891edd336 | |||
| 10da11cd7c | |||
| 71b5865d5e | |||
| ce54e385c5 | |||
| 7d333f994e | |||
| 617822fa18 | |||
| 86c06c4fd3 | |||
| d57c449aef | |||
| 9fe7496261 | |||
| 9b568ab4ae | |||
| e9d5d6c5cd | |||
| 2094d40ecb | |||
| 315bda9b48 | |||
| db5bb8b9c2 | |||
| 6d982db6b9 | |||
| 65fdde3e36 | |||
| 33cbcd6fc7 | |||
| 5ce2bb6527 | |||
| 7e3cec5aac | |||
| 606d9947d4 | |||
| aea9218162 | |||
| 9885f72a66 | |||
| d63a8e31c3 | |||
| 32afbc065b | |||
| 08d84cf629 | |||
| 85553f691f | |||
| 2c6aa8f8b3 | |||
| 80c11f76aa | |||
| aa853e8386 | |||
| 99890b57bc | |||
| ab5cd1c511 | |||
| b7a822ee41 | |||
| c9787e2102 | |||
| 8bfb69b93d | |||
| 8e62a47f6d | |||
| b8a774eecc | |||
| 6fb0b01a17 | |||
| 5fa5a513d2 | |||
| 5f122784f8 | |||
| f3003bad20 | |||
| 0afb42bb0f | |||
| 8db59ba9a1 | |||
| 5bf883a456 | |||
| d4ef6d6431 | |||
| ea34deeb2b | |||
| 442a577506 | |||
| fcc1d8672a | |||
| 429f2969aa | |||
| 7642097291 | |||
| bd14c560d2 | |||
| 18ca3e6ace | |||
| b77b9b0d20 | |||
| c2690f925c | |||
| f409965c8e | |||
| 566e0f90e2 | |||
| b98e920efb | |||
| 90940b23f3 | |||
| 2c9fa04a43 | |||
| eebb223bc3 | |||
| f99d6643cd | |||
| 37e5de729f | |||
| 811ca89fd4 | |||
| 6dbebaed5a | |||
| ee4f8698da | |||
| e31ea3cbe9 | |||
| b60bbc8b88 | |||
| a577e62ece | |||
| d14d35610e | |||
| 301234aa4f | |||
| 7a7edea189 | |||
| a6f25595f3 | |||
| 5af091880f | |||
| 4c165c2069 | |||
| 65df7be823 | |||
| 2c6e278167 | |||
| 68525d1426 | |||
| 1da197fdf5 | |||
| e545829bf5 | |||
| 982cc6a3bc | |||
| dc139d25d1 | |||
| 4f9d4f170f | |||
| 55344516ea | |||
| 454b155bbc | |||
| 808f666399 | |||
| d1f1287e77 | |||
| 0c30cc1bfc | |||
| e80c23980a | |||
| 50a631304d | |||
| bfc7bcdc4b | |||
| 3376836752 | |||
| 2ba2706104 | |||
| 58e97f1b36 | |||
| 6c71f15866 | |||
| 3b495615c2 | |||
| ba5f8a4160 | |||
| 88bd51ce74 | |||
| cc1057d910 | |||
| a3e6fa320f | |||
| 89492da7d6 | |||
| 01d7eac7ad | |||
| 1777f1aac8 | |||
| e17689e16e | |||
| 75c47a88ff | |||
| c3190b152a | |||
| fc5bd76334 | |||
| 13d7ee6b44 | |||
| 1ab6e6b78c | |||
| 6a67124e1c | |||
| b2c27143a8 | |||
| 206a224bf6 | |||
| a318e3f261 | |||
| eeee9c6bb1 | |||
| e51bb075e7 | |||
| 2ce8321178 | |||
| 68a0b74215 | |||
| aca2d94625 | |||
| cad90a3af6 | |||
| 5b90067ffc | |||
| 8eb1c034b1 | |||
| 66334131ca | |||
| 0bdaa52533 | |||
| 9d61ceec50 | |||
| e376ce7fd1 | |||
| cc2d4fa707 | |||
| 077e8f6daa | |||
| 190bcbdd78 | |||
| 7e3e1c0bc7 | |||
| 80d92cbc48 | |||
| 7c07387121 | |||
| 4303a7e963 | |||
| bc82e739f2 | |||
| d7fee058b7 | |||
| 569259d3df | |||
| ef6adb8123 | |||
| ec86901e97 | |||
| 64d9c82f71 | |||
| 0f130d4358 | |||
| fc0c183eec | |||
| 259f2481bd | |||
| 96fa086667 | |||
| f073a75bbe | |||
| 8c9a2a824a | |||
| 7625aa5c62 | |||
| d9534cdda7 | |||
| 08e860e457 | |||
| 8da7660d83 | |||
| d91e865e62 | |||
| 7986a926bf | |||
| 575dbe85b2 | |||
| e7224b67fd | |||
| e861f39149 |
@@ -0,0 +1,30 @@
|
||||
name: Publish
|
||||
on:
|
||||
workflow_dispatch
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-username: MAVEN_USERNAME # env variable for username in deploy
|
||||
server-password: MAVEN_PASSWORD # env variable for token in deploy
|
||||
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
|
||||
- name: Download drivers
|
||||
shell: bash
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Publish to Maven Central
|
||||
run: mvn deploy --batch-mode -D skipTests --activate-profiles release --no-transfer-progress
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
|
||||
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
|
||||
@@ -1,9 +1,13 @@
|
||||
name: Test
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
@@ -26,12 +30,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
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Test CLI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- release-*
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- name: Download drivers
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Intall Playwright
|
||||
run: mvn install -D skipTests --no-transfer-progress
|
||||
- name: Test CLI
|
||||
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
# 🎭 [Playwright](https://github.com/microsoft/playwright) for Java
|
||||
# 🎭 [Playwright](https://playwright.dev) for Java
|
||||
|
||||
[](https://search.maven.org/search?q=com.microsoft.playwright) [](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
|
||||
[](https://javadoc.io/doc/com.microsoft.playwright/playwright)
|
||||
[](https://search.maven.org/search?q=com.microsoft.playwright)
|
||||
[](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
|
||||
[](https://aka.ms/playwright-slack)
|
||||
|
||||
### _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 -->90.0.4392.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->14.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->85.0b5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/#?path=docs/intro.md&q=system-requirements) for details.
|
||||
|
||||
* [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.162.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
<version>0.162.3</version>
|
||||
<version>0.180.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.
|
||||
@@ -37,23 +67,22 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class PageScreenshot {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Playwright playwright = Playwright.create();
|
||||
List<BrowserType> browserTypes = Arrays.asList(
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
List<BrowserType> browserTypes = Arrays.asList(
|
||||
playwright.chromium(),
|
||||
playwright.webkit(),
|
||||
playwright.firefox()
|
||||
);
|
||||
for (BrowserType browserType : browserTypes) {
|
||||
Browser browser = browserType.launch();
|
||||
BrowserContext context = browser.newContext(
|
||||
new Browser.NewContextOptions().withViewport(800, 600));
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://whatsmyuseragent.org/");
|
||||
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
|
||||
browser.close();
|
||||
);
|
||||
for (BrowserType browserType : browserTypes) {
|
||||
try (Browser browser = browserType.launch()) {
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://whatsmyuseragent.org/");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
playwright.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -63,36 +92,36 @@ public class PageScreenshot {
|
||||
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
|
||||
|
||||
```java
|
||||
import com.microsoft.playwright.options.*;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class MobileAndGeolocation {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Playwright playwright = Playwright.create();
|
||||
BrowserType browserType = playwright.chromium();
|
||||
Browser browser = browserType.launch();
|
||||
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())
|
||||
.withLocale("en-US")
|
||||
.withGeolocation(new Geolocation(41.889938, 12.492507))
|
||||
.withPermissions(asList("geolocation")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.openstreetmap.org/");
|
||||
page.click("a[data-original-title=\"Show My Location\"]");
|
||||
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
|
||||
browser.close();
|
||||
playwright.close();
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch();
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
|
||||
.setViewportSize(411, 731)
|
||||
.setDeviceScaleFactor(2.625)
|
||||
.setIsMobile(true)
|
||||
.setHasTouch(true)
|
||||
.setLocale("en-US")
|
||||
.setGeolocation(41.889938, 12.492507)
|
||||
.setPermissions(asList("geolocation")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.openstreetmap.org/");
|
||||
page.click("a[data-original-title=\"Show My Location\"]");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
@@ -100,23 +129,21 @@ This code snippet navigates to example.com in Firefox, and executes a script in
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class EvaluateInBrowserContext {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Playwright playwright = Playwright.create();
|
||||
BrowserType browserType = playwright.firefox();
|
||||
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.example.com/");
|
||||
Object dimensions = page.evaluate("() => {\n" +
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.example.com/");
|
||||
Object dimensions = page.evaluate("() => {\n" +
|
||||
" return {\n" +
|
||||
" width: document.documentElement.clientWidth,\n" +
|
||||
" height: document.documentElement.clientHeight,\n" +
|
||||
" deviceScaleFactor: window.devicePixelRatio\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
System.out.println(dimensions);
|
||||
browser.close();
|
||||
playwright.close();
|
||||
System.out.println(dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -129,26 +156,31 @@ This code snippet sets up request routing for a WebKit page to log all network r
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class InterceptNetworkRequests {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Playwright playwright = Playwright.create();
|
||||
BrowserType browserType = playwright.webkit();
|
||||
Browser browser = browserType.launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.route("**", route -> {
|
||||
System.out.println(route.request().url());
|
||||
route.continue_();
|
||||
});
|
||||
page.navigate("http://todomvc.com");
|
||||
browser.close();
|
||||
playwright.close();
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.webkit().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.route("**", route -> {
|
||||
System.out.println(route.request().url());
|
||||
route.resume();
|
||||
});
|
||||
page.navigate("http://todomvc.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.162.4-SNAPSHOT</version>
|
||||
<version>1.9.1-alpha-0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
|
||||
@@ -29,32 +29,56 @@ public class DriverJar extends Driver {
|
||||
DriverJar() throws IOException, URISyntaxException, InterruptedException {
|
||||
driverTempDir = Files.createTempDirectory("playwright-java-");
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
System.err.println("extracting driver to " + driverTempDir);
|
||||
extractDriverToTempDir();
|
||||
installBrowsers();
|
||||
}
|
||||
|
||||
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) {
|
||||
System.err.println("Timed out waiting for browsers to install");
|
||||
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();
|
||||
System.out.println(uri);
|
||||
// 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);
|
||||
// jar file system's .relativize gives wrong results when used with
|
||||
// spring-boot-maven-plugin, convert to the default filesystem to
|
||||
// have predictable results.
|
||||
// See https://github.com/microsoft/playwright-java/issues/306
|
||||
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
|
||||
Files.walk(srcRoot).forEach(fromPath -> {
|
||||
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
|
||||
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);
|
||||
}
|
||||
@@ -76,14 +100,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
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.162.4-SNAPSHOT</version>
|
||||
<version>1.9.1-alpha-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) {
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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.190.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>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>snapshots-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases><enabled>false</enabled></releases>
|
||||
<snapshots><enabled>true</enabled></snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class EvaluateInBrowserContext {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.example.com/");
|
||||
Object dimensions = page.evaluate("() => {\n" +
|
||||
" return {\n" +
|
||||
" width: document.documentElement.clientWidth,\n" +
|
||||
" height: document.documentElement.clientHeight,\n" +
|
||||
" deviceScaleFactor: window.devicePixelRatio\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
System.out.println(dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 InterceptNetworkRequests {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.webkit().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.route("**", route -> {
|
||||
System.out.println(route.request().url());
|
||||
route.resume();
|
||||
});
|
||||
page.navigate("http://todomvc.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.example;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class MobileAndGeolocation {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch();
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
|
||||
.setViewportSize(411, 731)
|
||||
.setDeviceScaleFactor(2.625)
|
||||
.setIsMobile(true)
|
||||
.setHasTouch(true)
|
||||
.setLocale("en-US")
|
||||
.setGeolocation(41.889938, 12.492507)
|
||||
.setPermissions(asList("geolocation")));
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://www.openstreetmap.org/");
|
||||
page.click("a[data-original-title=\"Show My Location\"]");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class PageScreenshot {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
List<BrowserType> browserTypes = Arrays.asList(
|
||||
playwright.chromium(),
|
||||
playwright.webkit(),
|
||||
playwright.firefox()
|
||||
);
|
||||
for (BrowserType browserType : browserTypes) {
|
||||
try (Browser browser = browserType.launch()) {
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("http://whatsmyuseragent.org/");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class PrintTitle {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch();
|
||||
Page page = browser.newPage();
|
||||
page.navigate("http://playwright.dev");
|
||||
System.out.println(page.title());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class WebKitScreenshot {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.webkit().launch();
|
||||
Page page = browser.newPage();
|
||||
page.navigate("http://whatsmyuseragent.org/");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.162.4-SNAPSHOT</version>
|
||||
<version>1.9.1-alpha-0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
@@ -61,6 +61,10 @@
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
@@ -72,12 +76,6 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
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>
|
||||
* 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.
|
||||
*/
|
||||
public interface Accessibility {
|
||||
class SnapshotOptions {
|
||||
/**
|
||||
* Prune uninteresting nodes from the tree. Defaults to {@code true}.
|
||||
*/
|
||||
public Boolean interestingOnly;
|
||||
/**
|
||||
* The root DOM element for the snapshot. Defaults to the whole page.
|
||||
*/
|
||||
public ElementHandle root;
|
||||
|
||||
public SnapshotOptions withInterestingOnly(Boolean interestingOnly) {
|
||||
this.interestingOnly = interestingOnly;
|
||||
return this;
|
||||
}
|
||||
public SnapshotOptions withRoot(ElementHandle root) {
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
default AccessibilityNode snapshot() {
|
||||
return snapshot(null);
|
||||
}
|
||||
/**
|
||||
* Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
|
||||
* <p>
|
||||
* page.
|
||||
* <p>
|
||||
* <strong>NOTE</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AccessibilityNode {
|
||||
String role();
|
||||
String name();
|
||||
String valueString();
|
||||
Double valueNumber();
|
||||
String description();
|
||||
String keyshortcuts();
|
||||
String roledescription();
|
||||
String valuetext();
|
||||
Boolean disabled();
|
||||
Boolean expanded();
|
||||
Boolean focused();
|
||||
Boolean modal();
|
||||
Boolean multiline();
|
||||
Boolean multiselectable();
|
||||
Boolean readonly();
|
||||
Boolean required();
|
||||
Boolean selected();
|
||||
enum CheckedState { CHECKED, UNCHECKED, MIXED }
|
||||
CheckedState checked();
|
||||
enum PressedState { PRESSED, RELEASED, MIXED }
|
||||
PressedState pressed();
|
||||
Integer level();
|
||||
Double valuemin();
|
||||
Double valuemax();
|
||||
String autocomplete();
|
||||
String haspopup();
|
||||
String invalid();
|
||||
String orientation();
|
||||
List<AccessibilityNode> children();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Use this class to launch playwright cli.
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Path driver = Driver.ensureDriverInstalled();
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
pb.command().addAll(asList(args));
|
||||
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
|
||||
pb.environment().put("PW_CLI_TARGET_LANG", "java");
|
||||
}
|
||||
pb.inheritIO();
|
||||
Process process = pb.start();
|
||||
System.exit(process.waitFor());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE }
|
||||
@@ -19,42 +19,19 @@ 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 {@link Page#onConsole Page.onConsole()} event.
|
||||
*/
|
||||
public interface ConsoleMessage {
|
||||
class Location {
|
||||
/**
|
||||
* URL of the resource if available, otherwise empty string.
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 0-based line number in the resource.
|
||||
*/
|
||||
private int lineNumber;
|
||||
/**
|
||||
* 0-based column number in the resource.
|
||||
*/
|
||||
private int columnNumber;
|
||||
|
||||
public String url() {
|
||||
return this.url;
|
||||
}
|
||||
public int lineNumber() {
|
||||
return this.lineNumber;
|
||||
}
|
||||
public int columnNumber() {
|
||||
return this.columnNumber;
|
||||
}
|
||||
}
|
||||
List<JSHandle> args();
|
||||
Location location();
|
||||
/**
|
||||
* URL of the resource followed by 0-based line and column numbers in the resource formatted as {@code URL:line:column}.
|
||||
*/
|
||||
String location();
|
||||
String text();
|
||||
/**
|
||||
* One of the following values: {@code 'log'}, {@code 'debug'}, {@code 'info'}, {@code 'error'}, {@code 'warning'}, {@code 'dir'}, {@code 'dirxml'}, {@code 'table'},
|
||||
* <p>
|
||||
* {@code 'trace'}, {@code 'clear'}, {@code 'startGroup'}, {@code 'startGroupCollapsed'}, {@code 'endGroup'}, {@code 'assert'}, {@code 'profile'}, {@code 'profileEnd'},
|
||||
* <p>
|
||||
* {@code 'count'}, {@code 'timeEnd'}.
|
||||
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code "dir"}, {@code "dirxml"}, {@code "table"},
|
||||
* {@code "trace"}, {@code "clear"}, {@code "startGroup"}, {@code "startGroupCollapsed"}, {@code "endGroup"}, {@code "assert"}, {@code "profile"}, {@code "profileEnd"},
|
||||
* {@code "count"}, {@code "timeEnd"}.
|
||||
*/
|
||||
String type();
|
||||
}
|
||||
|
||||
@@ -19,16 +19,45 @@ 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 {@link Page#onDialog Page.onDialog()} event.
|
||||
*
|
||||
* <p> An example of using {@code Dialog} class:
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.*;
|
||||
*
|
||||
* public class Example {
|
||||
* public static void main(String[] args) {
|
||||
* try (Playwright playwright = Playwright.create()) {
|
||||
* BrowserType chromium = playwright.chromium();
|
||||
* Browser browser = chromium.launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.onDialog(dialog -> {
|
||||
* System.out.println(dialog.message());
|
||||
* dialog.dismiss();
|
||||
* });
|
||||
* page.evaluate("alert('1')");
|
||||
* browser.close();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link Page#onDialog Page.onDialog()} listener. When listener is
|
||||
* present, it **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()} the dialog
|
||||
* - otherwise the page will <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
|
||||
* dialog, and actions like click will never finish.
|
||||
*/
|
||||
public interface Dialog {
|
||||
enum Type { ALERT, BEFOREUNLOAD, CONFIRM, PROMPT }
|
||||
|
||||
/**
|
||||
* Returns when the dialog has been accepted.
|
||||
*/
|
||||
default void accept() {
|
||||
accept(null);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
@@ -47,6 +76,6 @@ public interface Dialog {
|
||||
/**
|
||||
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
|
||||
*/
|
||||
Type type();
|
||||
String type();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,21 +21,30 @@ 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 {@link Page#onDownload Page.onDownload()} event.
|
||||
*
|
||||
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
|
||||
* files are deleted when the browser closes.
|
||||
* <p>
|
||||
* Download event is emitted once the download starts. Download path becomes available once download completes:
|
||||
* <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:
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> page.click("a"));
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> {
|
||||
* page.click("a");
|
||||
* });
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the downloaded
|
||||
* content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not performed and user
|
||||
* has no access to the downloaded files.
|
||||
*/
|
||||
public interface Download {
|
||||
/**
|
||||
@@ -56,17 +65,16 @@ 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>
|
||||
* browsers can use different logic for computing it.
|
||||
* Returns suggested filename for this download. It is typically computed by the browser from the <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a> response
|
||||
* header or the {@code download} attribute. See the spec on <a
|
||||
* href="https://html.spec.whatwg.org/#downloading-resources">whatwg</a>. Different browsers can use different logic for
|
||||
* computing it.
|
||||
*/
|
||||
String suggestedFilename();
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,41 +16,37 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
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 {@link Page#onFileChooser Page.onFileChooser()} event.
|
||||
* <pre>{@code
|
||||
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
|
||||
* fileChooser.setFiles(Paths.get("myfile.pdf"));
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FileChooser {
|
||||
class FilePayload {
|
||||
public final String name;
|
||||
public final String mimeType;
|
||||
public final byte[] buffer;
|
||||
|
||||
public FilePayload(String name, String mimeType, byte[] buffer) {
|
||||
this.name = name;
|
||||
this.mimeType = mimeType;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
class SetFilesOptions {
|
||||
/**
|
||||
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can 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 {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
|
||||
* Page.setDefaultTimeout()} methods.
|
||||
*/
|
||||
public Integer timeout;
|
||||
public Double timeout;
|
||||
|
||||
public SetFilesOptions withNoWaitAfter(Boolean noWaitAfter) {
|
||||
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
|
||||
this.noWaitAfter = noWaitAfter;
|
||||
return this;
|
||||
}
|
||||
public SetFilesOptions withTimeout(Integer timeout) {
|
||||
public SetFilesOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
@@ -67,20 +63,53 @@ public interface FileChooser {
|
||||
* Returns page this file chooser belongs to.
|
||||
*/
|
||||
Page page();
|
||||
default void setFiles(Path file) { setFiles(file, null); }
|
||||
default void setFiles(Path file, SetFilesOptions options) { setFiles(new Path[]{ file }, options); }
|
||||
default void setFiles(Path[] files) { setFiles(files, null); }
|
||||
void setFiles(Path[] files, SetFilesOptions options);
|
||||
default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); }
|
||||
default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); }
|
||||
default void setFiles(FileChooser.FilePayload[] files) { setFiles(files, null); }
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* <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);
|
||||
default void setFiles(Path files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path[] files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload[] files, SetFilesOptions options);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,87 +19,131 @@ 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 {@link Page#evaluateHandle
|
||||
* Page.evaluateHandle()} method.
|
||||
* <pre>{@code
|
||||
* JSHandle windowHandle = page.evaluateHandle("() => window");
|
||||
* // ...
|
||||
* }</pre>
|
||||
*
|
||||
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with {@link
|
||||
* JSHandle#dispose JSHandle.dispose()}. JSHandles are auto-disposed when their origin frame gets navigated or the parent
|
||||
* context gets destroyed.
|
||||
*
|
||||
* <p> JSHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()}, {@link Page#evaluate
|
||||
* Page.evaluate()} and {@link Page#evaluateHandle 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();
|
||||
/**
|
||||
* The {@code jsHandle.dispose} method stops referencing the element handle.
|
||||
*/
|
||||
void dispose();
|
||||
default Object evaluate(String pageFunction) {
|
||||
return evaluate(pageFunction, null);
|
||||
/**
|
||||
* Returns the return value of {@code expression}.
|
||||
*
|
||||
* <p> This method passes this handle as the first argument to {@code expression}.
|
||||
*
|
||||
* <p> If {@code expression} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
|
||||
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> Examples:
|
||||
* <pre>{@code
|
||||
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
|
||||
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
|
||||
* }</pre>
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
*/
|
||||
default Object evaluate(String expression) {
|
||||
return evaluate(expression, null);
|
||||
}
|
||||
/**
|
||||
* 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>
|
||||
* value.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <p>
|
||||
*
|
||||
* @param pageFunction Function to be evaluated in browser context
|
||||
* @param arg Optional argument to pass to {@code pageFunction}
|
||||
* Returns the return value of {@code expression}.
|
||||
*
|
||||
* <p> This method passes this handle as the first argument to {@code expression}.
|
||||
*
|
||||
* <p> If {@code expression} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
|
||||
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> Examples:
|
||||
* <pre>{@code
|
||||
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
|
||||
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
|
||||
* }</pre>
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
* @param arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
Object evaluate(String pageFunction, Object arg);
|
||||
default JSHandle evaluateHandle(String pageFunction) {
|
||||
return evaluateHandle(pageFunction, null);
|
||||
Object evaluate(String expression, Object arg);
|
||||
/**
|
||||
* Returns the return value of {@code expression} as a {@code JSHandle}.
|
||||
*
|
||||
* <p> This method passes this handle as the first argument to {@code expression}.
|
||||
*
|
||||
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
|
||||
* {@code JSHandle}.
|
||||
*
|
||||
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
|
||||
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
*/
|
||||
default JSHandle evaluateHandle(String expression) {
|
||||
return evaluateHandle(expression, null);
|
||||
}
|
||||
/**
|
||||
* 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>
|
||||
* in-page object (JSHandle).
|
||||
* <p>
|
||||
* If the function passed to the {@code jsHandle.evaluateHandle} returns a Promise, then {@code jsHandle.evaluateHandle} would wait
|
||||
* <p>
|
||||
* for the promise to resolve and return its value.
|
||||
* <p>
|
||||
* See page.evaluateHandle(pageFunction[, arg]) for more details.
|
||||
* @param pageFunction Function to be evaluated
|
||||
* @param arg Optional argument to pass to {@code pageFunction}
|
||||
* Returns the return value of {@code expression} as a {@code JSHandle}.
|
||||
*
|
||||
* <p> This method passes this handle as the first argument to {@code expression}.
|
||||
*
|
||||
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
|
||||
* {@code JSHandle}.
|
||||
*
|
||||
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
|
||||
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
* @param arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
JSHandle evaluateHandle(String pageFunction, Object arg);
|
||||
JSHandle evaluateHandle(String expression, Object arg);
|
||||
/**
|
||||
* The method returns a map with **own property names** as keys and JSHandle instances for the property values.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
* JSHandle handle = page.evaluateHandle("() => ({window, document}"););
|
||||
* Map<String, JSHandle> properties = handle.getProperties();
|
||||
* JSHandle windowHandle = properties.get("window");
|
||||
* JSHandle documentHandle = properties.get("document");
|
||||
* handle.dispose();
|
||||
* }</pre>
|
||||
*/
|
||||
Map<String, JSHandle> getProperties();
|
||||
/**
|
||||
* 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>
|
||||
* error if the object has circular references.
|
||||
* 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();
|
||||
}
|
||||
|
||||
@@ -16,113 +16,216 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Keyboard provides an api for managing a virtual keyboard. The high level api is 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 {@link Keyboard#type Keyboard.type()},
|
||||
* which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page.
|
||||
*
|
||||
* <p> For finer control, you can use {@link Keyboard#down Keyboard.down()}, {@link Keyboard#up Keyboard.up()}, and {@link
|
||||
* Keyboard#insertText Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
|
||||
*
|
||||
* <p> An example of holding down {@code Shift} in order to select and delete some text:
|
||||
* <pre>{@code
|
||||
* page.keyboard().type("Hello World!");
|
||||
* page.keyboard().press("ArrowLeft");
|
||||
* page.keyboard().down("Shift");
|
||||
* for (int i = 0; i < " World".length(); i++)
|
||||
* page.keyboard().press("ArrowLeft");
|
||||
* page.keyboard().up("Shift");
|
||||
* page.keyboard().press("Backspace");
|
||||
* // Result text will end up saying "Hello!"
|
||||
* }</pre>
|
||||
*
|
||||
* <p> An example of pressing uppercase {@code A}
|
||||
* <pre>{@code
|
||||
* page.keyboard().press("Shift+KeyA");
|
||||
* // or
|
||||
* page.keyboard().press("Shift+A");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> An example to trigger select-all with the keyboard
|
||||
* <pre>{@code
|
||||
* // on Windows and Linux
|
||||
* page.keyboard().press("Control+A");
|
||||
* // on macOS
|
||||
* page.keyboard().press("Meta+A");
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Keyboard {
|
||||
enum Modifier { ALT, CONTROL, META, SHIFT }
|
||||
class PressOptions {
|
||||
/**
|
||||
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public Double delay;
|
||||
|
||||
public PressOptions setDelay(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 setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Dispatches a {@code keydown} event.
|
||||
* <p>
|
||||
* {@code key} can specify the intended keyboardEvent.key
|
||||
* <p>
|
||||
* 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>
|
||||
*
|
||||
* <p> {@code key} can specify the intended <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
|
||||
* character to generate the text for. A superset of the {@code key} values can be found <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
|
||||
*
|
||||
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
|
||||
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
|
||||
* <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 {@link Keyboard#up Keyboard.up()}.
|
||||
*
|
||||
* <p> After the key is pressed once, subsequent calls to {@link Keyboard#down Keyboard.down()} will have <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat">repeat</a> set to true. To release the key,
|
||||
* use {@link Keyboard#up Keyboard.up()}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
|
||||
*
|
||||
* @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.
|
||||
* <pre>{@code
|
||||
* page.keyboard().insertText("嗨");
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper case.
|
||||
*
|
||||
* @param text Sets input to the specified text value.
|
||||
*/
|
||||
void insertText(String text);
|
||||
default void press(String key) {
|
||||
press(key, 0);
|
||||
}
|
||||
/**
|
||||
* {@code key} can specify the intended keyboardEvent.key
|
||||
* <p>
|
||||
* 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>
|
||||
* {@code key} can specify the intended <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
|
||||
* character to generate the text for. A superset of the {@code key} values can be found <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
|
||||
*
|
||||
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
|
||||
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
|
||||
* <p>
|
||||
* Following modification shortcuts are also 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).
|
||||
* <pre>{@code
|
||||
* Page page = browser.newPage();
|
||||
* page.navigate("https://keycode.info");
|
||||
* page.keyboard().press("A");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
|
||||
* page.keyboard().press("ArrowLeft");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
|
||||
* page.keyboard().press("Shift+O");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
|
||||
*
|
||||
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
|
||||
*/
|
||||
void press(String key, int delay);
|
||||
default void press(String key) {
|
||||
press(key, null);
|
||||
}
|
||||
/**
|
||||
* {@code key} can specify the intended <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
|
||||
* character to generate the text for. A superset of the {@code key} values can be found <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
|
||||
*
|
||||
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
|
||||
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
|
||||
*
|
||||
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
|
||||
*
|
||||
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
|
||||
*
|
||||
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
|
||||
* texts.
|
||||
*
|
||||
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When speficied with the
|
||||
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
|
||||
* <pre>{@code
|
||||
* Page page = browser.newPage();
|
||||
* page.navigate("https://keycode.info");
|
||||
* page.keyboard().press("A");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
|
||||
* page.keyboard().press("ArrowLeft");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
|
||||
* page.keyboard().press("Shift+O");
|
||||
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
|
||||
*
|
||||
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
|
||||
*/
|
||||
void press(String key, PressOptions options);
|
||||
/**
|
||||
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
|
||||
*
|
||||
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
|
||||
* <pre>{@code
|
||||
* // Types instantly
|
||||
* page.keyboard().type("Hello");
|
||||
* // Types slower, like a user
|
||||
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
|
||||
*
|
||||
* @param text A text to type into a focused element.
|
||||
*/
|
||||
default void type(String text) {
|
||||
type(text, 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 {@link Keyboard#press Keyboard.press()}.
|
||||
* <pre>{@code
|
||||
* // Types instantly
|
||||
* page.keyboard().type("Hello");
|
||||
* // Types slower, like a user
|
||||
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
|
||||
*
|
||||
* @param text A text to type into a focused element.
|
||||
*/
|
||||
void type(String text, int delay);
|
||||
void type(String text, TypeOptions options);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -16,40 +16,48 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
|
||||
* <p>
|
||||
* Every {@code page} object has its own Mouse, accessible with page.mouse.
|
||||
* <p>
|
||||
*
|
||||
* <p> Every {@code page} object has its own Mouse, accessible with {@link Page#mouse Page.mouse()}.
|
||||
* <pre>{@code
|
||||
* // Using ‘page.mouse’ to trace a 100x100 square.
|
||||
* page.mouse().move(0, 0);
|
||||
* page.mouse().down();
|
||||
* page.mouse().move(0, 100);
|
||||
* page.mouse().move(100, 100);
|
||||
* page.mouse().move(100, 0);
|
||||
* page.mouse().move(0, 0);
|
||||
* page.mouse().up();
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Mouse {
|
||||
enum Button { LEFT, MIDDLE, RIGHT }
|
||||
|
||||
class ClickOptions {
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public Button button;
|
||||
public MouseButton button;
|
||||
/**
|
||||
* defaults to 1. See UIEvent.detail.
|
||||
* 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) {
|
||||
public ClickOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
public ClickOptions withClickCount(Integer clickCount) {
|
||||
public ClickOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
}
|
||||
public ClickOptions withDelay(Integer delay) {
|
||||
public ClickOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
}
|
||||
@@ -58,17 +66,17 @@ public interface Mouse {
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public Button button;
|
||||
public MouseButton button;
|
||||
/**
|
||||
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public Integer delay;
|
||||
public Double delay;
|
||||
|
||||
public DblclickOptions withButton(Button button) {
|
||||
public DblclickOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
public DblclickOptions withDelay(Integer delay) {
|
||||
public DblclickOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
}
|
||||
@@ -77,17 +85,17 @@ public interface Mouse {
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public Button button;
|
||||
public MouseButton button;
|
||||
/**
|
||||
* defaults to 1. See UIEvent.detail.
|
||||
* defaults to 1. See [UIEvent.detail].
|
||||
*/
|
||||
public Integer clickCount;
|
||||
|
||||
public DownOptions withButton(Button button) {
|
||||
public DownOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
public DownOptions withClickCount(Integer clickCount) {
|
||||
public DownOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
}
|
||||
@@ -98,7 +106,7 @@ public interface Mouse {
|
||||
*/
|
||||
public Integer steps;
|
||||
|
||||
public MoveOptions withSteps(Integer steps) {
|
||||
public MoveOptions setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
return this;
|
||||
}
|
||||
@@ -107,35 +115,46 @@ public interface Mouse {
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public Button button;
|
||||
public MouseButton button;
|
||||
/**
|
||||
* defaults to 1. See UIEvent.detail.
|
||||
* defaults to 1. See [UIEvent.detail].
|
||||
*/
|
||||
public Integer clickCount;
|
||||
|
||||
public UpOptions withButton(Button button) {
|
||||
public UpOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
public UpOptions withClickCount(Integer clickCount) {
|
||||
public UpOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
default void click(int x, int y) {
|
||||
/**
|
||||
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
|
||||
*/
|
||||
default void click(double x, double y) {
|
||||
click(x, y, null);
|
||||
}
|
||||
/**
|
||||
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]).
|
||||
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up 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);
|
||||
/**
|
||||
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
|
||||
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
|
||||
*/
|
||||
default void dblclick(double x, double y) {
|
||||
dblclick(x, y, null);
|
||||
}
|
||||
/**
|
||||
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]), mouse.down([options]) and mouse.up([options]).
|
||||
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
|
||||
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
|
||||
*/
|
||||
void dblclick(double x, double y, DblclickOptions options);
|
||||
/**
|
||||
* Dispatches a {@code mousedown} event.
|
||||
*/
|
||||
void dblclick(int x, int y, DblclickOptions options);
|
||||
default void down() {
|
||||
down(null);
|
||||
}
|
||||
@@ -143,13 +162,19 @@ public interface Mouse {
|
||||
* Dispatches a {@code mousedown} event.
|
||||
*/
|
||||
void down(DownOptions options);
|
||||
default void move(int x, int y) {
|
||||
/**
|
||||
* Dispatches a {@code mousemove} event.
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
* Dispatches a {@code mouseup} event.
|
||||
*/
|
||||
default void up() {
|
||||
up(null);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,22 +17,63 @@
|
||||
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:
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.*;
|
||||
*
|
||||
* public class Example {
|
||||
* public static void main(String[] args) {
|
||||
* try (Playwright playwright = Playwright.create()) {
|
||||
* BrowserType chromium = playwright.chromium();
|
||||
* Browser browser = chromium.launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.navigate("http://example.com");
|
||||
* // other actions...
|
||||
* browser.close();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Playwright extends AutoCloseable {
|
||||
/**
|
||||
* This object can be used to launch or connect to Chromium, returning instances of {@code ChromiumBrowser}.
|
||||
*/
|
||||
BrowserType chromium();
|
||||
/**
|
||||
* 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 <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
|
||||
*/
|
||||
Selectors selectors();
|
||||
/**
|
||||
* This object can be used to launch or connect to WebKit, returning instances of {@code WebKitBrowser}.
|
||||
*/
|
||||
BrowserType webkit();
|
||||
/**
|
||||
* Terminates this instance of Playwright, will also close all created browsers if they are still running.
|
||||
*/
|
||||
void close();
|
||||
/**
|
||||
* Launches new Playwright driver process and connects to it. {@link Playwright#close Playwright.close()} should be called
|
||||
* when the instance is no longer needed.
|
||||
* <pre>{@code
|
||||
* Playwright playwright = Playwright.create()) {
|
||||
* Browser browser = playwright.webkit().launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.navigate("https://www.w3.org/");
|
||||
* playwright.close();
|
||||
* }</pre>
|
||||
*/
|
||||
static Playwright create() {
|
||||
return PlaywrightImpl.create();
|
||||
}
|
||||
|
||||
BrowserType chromium();
|
||||
BrowserType firefox();
|
||||
BrowserType webkit();
|
||||
|
||||
Map<String, DeviceDescriptor> devices();
|
||||
|
||||
Selectors selectors();
|
||||
|
||||
@Override
|
||||
void close() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,118 +16,41 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Whenever the page sends a request for a network resource the following sequence of events are emitted by 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}:
|
||||
* <ul>
|
||||
* <li> {@link Page#onRequest Page.onRequest()} emitted when the request is issued by the page.</li>
|
||||
* <li> {@link Page#onResponse Page.onResponse()} emitted when/if the response status and headers are received for the request.</li>
|
||||
* <li> {@link Page#onRequestFinished Page.onRequestFinished()} emitted when the response body is downloaded and the request is
|
||||
* complete.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> If request fails at some point, then instead of {@code "requestfinished"} event (and possibly instead of 'response' event),
|
||||
* the {@link Page#onRequestFailed Page.onRequestFailed()} event is emitted.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
|
||||
* with {@code "requestfinished"} event.
|
||||
*
|
||||
* <p> If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new
|
||||
* request is issued to a redirected url.
|
||||
*/
|
||||
public interface Request {
|
||||
class RequestFailure {
|
||||
/**
|
||||
* Human-readable error message, e.g. {@code 'net::ERR_FAILED'}.
|
||||
*/
|
||||
private String errorText;
|
||||
|
||||
public RequestFailure(String errorText) {
|
||||
this.errorText = errorText;
|
||||
}
|
||||
public String errorText() {
|
||||
return this.errorText;
|
||||
}
|
||||
}
|
||||
class RequestTiming {
|
||||
/**
|
||||
* Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC
|
||||
*/
|
||||
private int startTime;
|
||||
/**
|
||||
* Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int domainLookupStart;
|
||||
/**
|
||||
* Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int domainLookupEnd;
|
||||
/**
|
||||
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int connectStart;
|
||||
/**
|
||||
* Time immediately before the browser starts the handshake process to secure the current connection. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int secureConnectionStart;
|
||||
/**
|
||||
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int connectEnd;
|
||||
/**
|
||||
* Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int requestStart;
|
||||
/**
|
||||
* Time immediately after the browser starts requesting the resource from the server, cache, or local resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int responseStart;
|
||||
/**
|
||||
* Time immediately after the browser receives the last byte of the resource or immediately before the transport connection is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
|
||||
*/
|
||||
private int responseEnd;
|
||||
|
||||
public int startTime() {
|
||||
return this.startTime;
|
||||
}
|
||||
public int domainLookupStart() {
|
||||
return this.domainLookupStart;
|
||||
}
|
||||
public int domainLookupEnd() {
|
||||
return this.domainLookupEnd;
|
||||
}
|
||||
public int connectStart() {
|
||||
return this.connectStart;
|
||||
}
|
||||
public int secureConnectionStart() {
|
||||
return this.secureConnectionStart;
|
||||
}
|
||||
public int connectEnd() {
|
||||
return this.connectEnd;
|
||||
}
|
||||
public int requestStart() {
|
||||
return this.requestStart;
|
||||
}
|
||||
public int responseStart() {
|
||||
return this.responseStart;
|
||||
}
|
||||
public int 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:
|
||||
* <pre>{@code
|
||||
* page.onRequestFailed(request -> {
|
||||
* System.out.println(request.url() + " " + request.failure());
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
RequestFailure failure();
|
||||
String failure();
|
||||
/**
|
||||
* Returns the Frame that initiated this request.
|
||||
* Returns the {@code Frame} that initiated this request.
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
@@ -152,47 +75,56 @@ 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}:
|
||||
* <pre>{@code
|
||||
* Response response = page.navigate("http://example.com");
|
||||
* System.out.println(response.request().redirectedFrom().url()); // "http://example.com"
|
||||
* }</pre>
|
||||
*
|
||||
* <p> If the website {@code https://google.com} has no redirects:
|
||||
* <pre>{@code
|
||||
* Response response = page.navigate("https://google.com");
|
||||
* System.out.println(response.request().redirectedFrom()); // null
|
||||
* }</pre>
|
||||
*/
|
||||
Request redirectedFrom();
|
||||
/**
|
||||
* New request issued by the browser if the server responded with redirect.
|
||||
* <p>
|
||||
* This method is the opposite of request.redirectedFrom():
|
||||
* <p>
|
||||
*
|
||||
* <p> This method is the opposite of {@link Request#redirectedFrom Request.redirectedFrom()}:
|
||||
* <pre>{@code
|
||||
* System.out.println(request.redirectedFrom().redirectedTo() == request); // true
|
||||
* }</pre>
|
||||
*/
|
||||
Request redirectedTo();
|
||||
/**
|
||||
* 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 <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">Resource Timing API</a>.
|
||||
* <pre>{@code
|
||||
* page.onRequestFinished(request -> {
|
||||
* Timing timing = request.timing();
|
||||
* System.out.println(timing.responseEnd - timing.startTime);
|
||||
* });
|
||||
* page.navigate("http://example.com");
|
||||
* }</pre>
|
||||
*/
|
||||
RequestTiming timing();
|
||||
Timing timing();
|
||||
/**
|
||||
* URL of the request.
|
||||
*/
|
||||
|
||||
@@ -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 {@link Page#route Page.route()} or {@link BrowserContext#route
|
||||
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
|
||||
*/
|
||||
public interface Route {
|
||||
class ContinueOverrides {
|
||||
class ResumeOptions {
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
@@ -38,118 +37,185 @@ public interface Route {
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public byte[] postData;
|
||||
public Object 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 ResumeOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
public ResumeOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
public ResumeOptions setPostData(String postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
public ResumeOptions setPostData(byte[] postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
public ResumeOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FulfillResponse {
|
||||
class FulfillOptions {
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
* Optional response body as text.
|
||||
*/
|
||||
public int status;
|
||||
public String body;
|
||||
/**
|
||||
* Optional response headers. Header values will be converted to a string.
|
||||
* Optional response body as raw bytes.
|
||||
*/
|
||||
public Map<String, String> headers;
|
||||
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) {
|
||||
this.bodyBytes = body;
|
||||
return this;
|
||||
}
|
||||
public FulfillResponse withBody(String body) {
|
||||
public FulfillOptions setBody(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
public FulfillResponse withPath(Path path) {
|
||||
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
|
||||
this.bodyBytes = bodyBytes;
|
||||
return this;
|
||||
}
|
||||
public FulfillOptions setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
public FulfillOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
public FulfillOptions setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
public FulfillOptions setStatus(int status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Aborts the route's request.
|
||||
*/
|
||||
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.
|
||||
* <ul>
|
||||
* <li> {@code "aborted"} - An operation was aborted (due to user action)</li>
|
||||
* <li> {@code "accessdenied"} - Permission to access a resource, other than the network, was denied</li>
|
||||
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified host
|
||||
* or network.</li>
|
||||
* <li> {@code "blockedbyclient"} - The client chose to block the request.</li>
|
||||
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are not met
|
||||
* ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
|
||||
* <li> {@code "connectionaborted"} - A connection timed out as a result of not receiving an ACK for data sent.</li>
|
||||
* <li> {@code "connectionclosed"} - A connection was closed (corresponding to a TCP FIN).</li>
|
||||
* <li> {@code "connectionfailed"} - A connection attempt failed.</li>
|
||||
* <li> {@code "connectionrefused"} - A connection attempt was refused.</li>
|
||||
* <li> {@code "connectionreset"} - A connection was reset (corresponding to a TCP RST).</li>
|
||||
* <li> {@code "internetdisconnected"} - The Internet connection has been lost.</li>
|
||||
* <li> {@code "namenotresolved"} - The host name could not be resolved.</li>
|
||||
* <li> {@code "timedout"} - An operation timed out.</li>
|
||||
* <li> {@code "failed"} - A generic failure occurred.</li>
|
||||
* </ul>
|
||||
*/
|
||||
void abort(String errorCode);
|
||||
default void continue_() {
|
||||
continue_(null);
|
||||
/**
|
||||
* Continues route's request with optional overrides.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
default void resume() {
|
||||
resume(null);
|
||||
}
|
||||
/**
|
||||
* Continues route's request with optional overrides.
|
||||
* <p>
|
||||
*
|
||||
* @param overrides Optional request overrides, can override following properties:
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
void continue_(ContinueOverrides overrides);
|
||||
void resume(ResumeOptions options);
|
||||
/**
|
||||
* Fulfills route's request with given response.
|
||||
* @param response Response that will fulfill this route's request.
|
||||
*
|
||||
* <p> An example of fulfilling all requests with 404 responses:
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* route.fulfill(new Route.FulfillOptions()
|
||||
* .setStatus(404)
|
||||
* .setContentType("text/plain")
|
||||
* .setBody("Not Found!"));
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> An example of serving static file:
|
||||
* <pre>{@code
|
||||
* page.route("**\/xhr_endpoint", route -> route.fulfill(
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
|
||||
* }</pre>
|
||||
*/
|
||||
void fulfill(FulfillResponse response);
|
||||
default void fulfill() {
|
||||
fulfill(null);
|
||||
}
|
||||
/**
|
||||
* Fulfills route's request with given response.
|
||||
*
|
||||
* <p> An example of fulfilling all requests with 404 responses:
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* route.fulfill(new Route.FulfillOptions()
|
||||
* .setStatus(404)
|
||||
* .setContentType("text/plain")
|
||||
* .setBody("Not Found!"));
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> An example of serving static file:
|
||||
* <pre>{@code
|
||||
* page.route("**\/xhr_endpoint", route -> route.fulfill(
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
|
||||
* }</pre>
|
||||
*/
|
||||
void fulfill(FulfillOptions options);
|
||||
/**
|
||||
* A request to be routed.
|
||||
*/
|
||||
|
||||
@@ -20,32 +20,158 @@ 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 <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> 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 setContentScript(boolean contentScript) {
|
||||
this.contentScript = contentScript;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
default void register(String name, String script) { register(name, script, null); }
|
||||
void register(String name, String script, RegisterOptions options);
|
||||
default void register(String name, Path path) { register(name, path, null); }
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <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.
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
* " return root.querySelector(selector);\n" +
|
||||
* " },\n" +
|
||||
* " // Returns all elements matching given selector in the root's subtree.\n" +
|
||||
* " queryAll(root, selector) {\n" +
|
||||
* " return Array.from(root.querySelectorAll(selector));\n" +
|
||||
* " }\n" +
|
||||
* "}";
|
||||
* // Register the engine. Selectors will be prefixed with "tag=".
|
||||
* playwright.selectors().register("tag", createTagNameEngine);
|
||||
* Browser browser = playwright.firefox().launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
void register(String name, Path path, RegisterOptions options);
|
||||
default void register(String name, String script) {
|
||||
register(name, script, null);
|
||||
}
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
* " return root.querySelector(selector);\n" +
|
||||
* " },\n" +
|
||||
* " // Returns all elements matching given selector in the root's subtree.\n" +
|
||||
* " queryAll(root, selector) {\n" +
|
||||
* " return Array.from(root.querySelectorAll(selector));\n" +
|
||||
* " }\n" +
|
||||
* "}";
|
||||
* // Register the engine. Selectors will be prefixed with "tag=".
|
||||
* playwright.selectors().register("tag", createTagNameEngine);
|
||||
* Browser browser = playwright.firefox().launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
void register(String name, String script, RegisterOptions options);
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
* " return root.querySelector(selector);\n" +
|
||||
* " },\n" +
|
||||
* " // Returns all elements matching given selector in the root's subtree.\n" +
|
||||
* " queryAll(root, selector) {\n" +
|
||||
* " return Array.from(root.querySelectorAll(selector));\n" +
|
||||
* " }\n" +
|
||||
* "}";
|
||||
* // Register the engine. Selectors will be prefixed with "tag=".
|
||||
* playwright.selectors().register("tag", createTagNameEngine);
|
||||
* Browser browser = playwright.firefox().launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
default void register(String name, Path script) {
|
||||
register(name, script, null);
|
||||
}
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
* " return root.querySelector(selector);\n" +
|
||||
* " },\n" +
|
||||
* " // Returns all elements matching given selector in the root's subtree.\n" +
|
||||
* " queryAll(root, selector) {\n" +
|
||||
* " return Array.from(root.querySelectorAll(selector));\n" +
|
||||
* " }\n" +
|
||||
* "}";
|
||||
* // Register the engine. Selectors will be prefixed with "tag=".
|
||||
* playwright.selectors().register("tag", createTagNameEngine);
|
||||
* Browser browser = playwright.firefox().launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
*/
|
||||
void register(String name, Path script, RegisterOptions options);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
|
||||
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]).
|
||||
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. {@link Page#waitForSelector
|
||||
* Page.waitForSelector()} or {@link BrowserType#launch BrowserType.launch()}.
|
||||
*/
|
||||
public interface TimeoutError {
|
||||
public class TimeoutError extends PlaywrightException {
|
||||
public TimeoutError(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TimeoutError(String message, Throwable exception) {
|
||||
super(message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,20 @@
|
||||
|
||||
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>
|
||||
* <pre>{@code
|
||||
* System.out.println(page.video().path());
|
||||
* }</pre>
|
||||
*/
|
||||
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,39 +17,90 @@
|
||||
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 {
|
||||
byte[] body();
|
||||
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) {
|
||||
/**
|
||||
* Fired when the websocket closes.
|
||||
*/
|
||||
void onClose(Consumer<WebSocket> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
|
||||
*/
|
||||
void offClose(Consumer<WebSocket> handler);
|
||||
|
||||
/**
|
||||
* Fired when the websocket recieves a frame.
|
||||
*/
|
||||
void onFrameReceived(Consumer<WebSocketFrame> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onFrameReceived onFrameReceived(handler)}.
|
||||
*/
|
||||
void offFrameReceived(Consumer<WebSocketFrame> handler);
|
||||
|
||||
/**
|
||||
* Fired when the websocket sends a frame.
|
||||
*/
|
||||
void onFrameSent(Consumer<WebSocketFrame> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onFrameSent onFrameSent(handler)}.
|
||||
*/
|
||||
void offFrameSent(Consumer<WebSocketFrame> handler);
|
||||
|
||||
/**
|
||||
* Fired when the websocket has an error.
|
||||
*/
|
||||
void onSocketError(Consumer<String> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onSocketError onSocketError(handler)}.
|
||||
*/
|
||||
void offSocketError(Consumer<String> handler);
|
||||
|
||||
class WaitForFrameReceivedOptions {
|
||||
/**
|
||||
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
|
||||
*/
|
||||
public Predicate<WebSocketFrame> predicate;
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
public WaitForFrameReceivedOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class WaitForFrameSentOptions {
|
||||
/**
|
||||
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
|
||||
*/
|
||||
public Predicate<WebSocketFrame> predicate;
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
FRAMERECEIVED,
|
||||
FRAMESENT,
|
||||
SOCKETERROR,
|
||||
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
public WaitForFrameSentOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
void addListener(EventType type, Listener<EventType> listener);
|
||||
void removeListener(EventType type, Listener<EventType> listener);
|
||||
/**
|
||||
* Indicates that the web socket has been closed.
|
||||
*/
|
||||
@@ -58,22 +109,41 @@ 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);
|
||||
/**
|
||||
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
|
||||
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
|
||||
* WebSocket or Page is closed before the frame is received.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
default WebSocketFrame waitForFrameReceived(Runnable callback) {
|
||||
return waitForFrameReceived(null, callback);
|
||||
}
|
||||
/**
|
||||
* 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)}.
|
||||
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
|
||||
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
|
||||
* WebSocket or Page is closed before the frame is received.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
|
||||
WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable callback);
|
||||
/**
|
||||
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
|
||||
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
|
||||
* WebSocket or Page is closed before the frame is sent.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
default WebSocketFrame waitForFrameSent(Runnable callback) {
|
||||
return waitForFrameSent(null, callback);
|
||||
}
|
||||
/**
|
||||
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
|
||||
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
|
||||
* WebSocket or Page is closed before the frame is sent.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
|
||||
* either {@link WebSocketFrame#text WebSocketFrame.text()} or {@link WebSocketFrame#binary WebSocketFrame.binary()} method
|
||||
* depending on the its type.
|
||||
*/
|
||||
public interface WebSocketFrame {
|
||||
/**
|
||||
* Returns binary payload.
|
||||
*/
|
||||
byte[] binary();
|
||||
/**
|
||||
* Returns text payload.
|
||||
*/
|
||||
String text();
|
||||
}
|
||||
|
||||
@@ -17,59 +17,124 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The Worker class represents a WebWorker. {@code worker}
|
||||
* <p>
|
||||
* 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>
|
||||
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
|
||||
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object
|
||||
* when the worker is gone.
|
||||
* <pre>{@code
|
||||
* page.onWorker(worker -> {
|
||||
* System.out.println("Worker created: " + worker.url());
|
||||
* worker.onClose(worker1 -> System.out.println("Worker destroyed: " + worker1.url()));
|
||||
* });
|
||||
* System.out.println("Current workers:");
|
||||
* for (Worker worker : page.workers())
|
||||
* System.out.println(" " + worker.url());
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Worker {
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
}
|
||||
|
||||
void addListener(EventType type, Listener<EventType> listener);
|
||||
void removeListener(EventType type, Listener<EventType> listener);
|
||||
default Object evaluate(String pageFunction) {
|
||||
return evaluate(pageFunction, null);
|
||||
/**
|
||||
* Emitted when this dedicated <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a> is
|
||||
* terminated.
|
||||
*/
|
||||
void onClose(Consumer<Worker> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
|
||||
*/
|
||||
void offClose(Consumer<Worker> handler);
|
||||
|
||||
class WaitForCloseOptions {
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public WaitForCloseOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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>
|
||||
* 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>
|
||||
* {@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}
|
||||
* Returns the return value of {@code expression}.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
|
||||
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
|
||||
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
|
||||
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
*/
|
||||
Object evaluate(String pageFunction, Object arg);
|
||||
default JSHandle evaluateHandle(String pageFunction) {
|
||||
return evaluateHandle(pageFunction, null);
|
||||
default Object evaluate(String expression) {
|
||||
return evaluate(expression, null);
|
||||
}
|
||||
/**
|
||||
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
|
||||
* <p>
|
||||
* The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
|
||||
* <p>
|
||||
* 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>
|
||||
* 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}
|
||||
* Returns the return value of {@code expression}.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
|
||||
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
|
||||
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
|
||||
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
* @param arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
JSHandle evaluateHandle(String pageFunction, Object arg);
|
||||
Object evaluate(String expression, Object arg);
|
||||
/**
|
||||
* Returns the return value of {@code expression} as a {@code JSHandle}.
|
||||
*
|
||||
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
|
||||
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
|
||||
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
*/
|
||||
default JSHandle evaluateHandle(String expression) {
|
||||
return evaluateHandle(expression, null);
|
||||
}
|
||||
/**
|
||||
* Returns the return value of {@code expression} as a {@code JSHandle}.
|
||||
*
|
||||
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
|
||||
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
|
||||
*
|
||||
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
|
||||
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
|
||||
*
|
||||
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
|
||||
* as a function. Otherwise, evaluated as an expression.
|
||||
* @param arg Optional argument to pass to {@code expression}.
|
||||
*/
|
||||
JSHandle evaluateHandle(String expression, Object arg);
|
||||
String url();
|
||||
Deferred<Event<EventType>> waitForEvent(EventType event);
|
||||
/**
|
||||
* Performs action and waits for the Worker to close.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
default Worker waitForClose(Runnable callback) {
|
||||
return waitForClose(null, callback);
|
||||
}
|
||||
/**
|
||||
* Performs action and waits for the Worker to close.
|
||||
*
|
||||
* @param callback Callback that performs the action triggering the event.
|
||||
*/
|
||||
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +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.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class Main {
|
||||
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));
|
||||
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")));
|
||||
browser.close();
|
||||
playwright.close();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Accessibility;
|
||||
import com.microsoft.playwright.AccessibilityNode;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class AccessibilityImpl implements Accessibility {
|
||||
private final PageImpl page;
|
||||
|
||||
AccessibilityImpl(PageImpl page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityNode snapshot(SnapshotOptions options) {
|
||||
if (options == null) {
|
||||
options = new SnapshotOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonObject json = page.sendMessage("accessibilitySnapshot", params).getAsJsonObject();
|
||||
if (!json.has("rootAXNode")) {
|
||||
return null;
|
||||
}
|
||||
return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode"));
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.AccessibilityNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class AccessibilityNodeImpl implements AccessibilityNode {
|
||||
private final JsonObject json;
|
||||
|
||||
AccessibilityNodeImpl(JsonObject json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String role() {
|
||||
return json.get("role").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return json.get("name").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valueString() {
|
||||
if (!json.has("valueString")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("valueString").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double valueNumber() {
|
||||
if (!json.has("valueNumber")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("valueNumber").getAsDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
if (!json.has("description")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("description").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String keyshortcuts() {
|
||||
if (!json.has("keyshortcuts")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("keyshortcuts").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String roledescription() {
|
||||
if (!json.has("roledescription")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("roledescription").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valuetext() {
|
||||
if (!json.has("valuetext")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("valuetext").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean disabled() {
|
||||
if (!json.has("disabled")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("disabled").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean expanded() {
|
||||
if (!json.has("expanded")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("expanded").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean focused() {
|
||||
if (!json.has("focused")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("focused").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean modal() {
|
||||
if (!json.has("modal")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("modal").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean multiline() {
|
||||
if (!json.has("multiline")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("multiline").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean multiselectable() {
|
||||
if (!json.has("multiselectable")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("multiselectable").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean readonly() {
|
||||
if (!json.has("readonly")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("readonly").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean required() {
|
||||
if (!json.has("required")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("required").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean selected() {
|
||||
if (!json.has("selected")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("selected").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckedState checked() {
|
||||
if (!json.has("checked")) {
|
||||
return null;
|
||||
}
|
||||
String value = json.get("checked").getAsString();
|
||||
switch (value) {
|
||||
case "checked": return CheckedState.CHECKED;
|
||||
case "unchecked": return CheckedState.UNCHECKED;
|
||||
case "mixed": return CheckedState.MIXED;
|
||||
default: throw new IllegalStateException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PressedState pressed() {
|
||||
if (!json.has("pressed")) {
|
||||
return null;
|
||||
}
|
||||
String value = json.get("pressed").getAsString();
|
||||
switch (value) {
|
||||
case "pressed": return PressedState.PRESSED;
|
||||
case "released": return PressedState.RELEASED;
|
||||
case "mixed": return PressedState.MIXED;
|
||||
default: throw new IllegalStateException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer level() {
|
||||
if (!json.has("level")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("level").getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double valuemin() {
|
||||
if (!json.has("valuemin")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("valuemin").getAsDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double valuemax() {
|
||||
if (!json.has("valuemax")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("valuemax").getAsDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String autocomplete() {
|
||||
if (!json.has("autocomplete")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("autocomplete").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String haspopup() {
|
||||
if (!json.has("haspopup")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("haspopup").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String invalid() {
|
||||
if (!json.has("invalid")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("invalid").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String orientation() {
|
||||
if (!json.has("orientation")) {
|
||||
return null;
|
||||
}
|
||||
return json.get("orientation").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityNode> children() {
|
||||
if (!json.has("children")) {
|
||||
return null;
|
||||
}
|
||||
List<AccessibilityNode> result = new ArrayList<>();
|
||||
for (JsonElement e : json.getAsJsonArray("children")) {
|
||||
result.add(new AccessibilityNodeImpl(e.getAsJsonObject()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.JSHandle;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.options.BindingCallback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -29,7 +30,7 @@ import java.util.List;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
|
||||
class BindingCall extends ChannelOwner {
|
||||
private static class SourceImpl implements Page.Binding.Source {
|
||||
private static class SourceImpl implements BindingCallback.Source {
|
||||
private final Frame frame;
|
||||
|
||||
public SourceImpl(Frame frame) {
|
||||
@@ -60,10 +61,10 @@ class BindingCall extends ChannelOwner {
|
||||
return initializer.get("name").getAsString();
|
||||
}
|
||||
|
||||
void call(Page.Binding binding) {
|
||||
void call(BindingCallback binding) {
|
||||
try {
|
||||
Frame frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
|
||||
Page.Binding.Source source = new SourceImpl(frame);
|
||||
BindingCallback.Source source = new SourceImpl(frame);
|
||||
List<Object> args = new ArrayList<>();
|
||||
if (initializer.has("handle")) {
|
||||
JSHandle handle = connection.getExistingObject(initializer.getAsJsonObject("handle").get("guid").getAsString());
|
||||
|
||||
@@ -20,31 +20,40 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final BrowserImpl browser;
|
||||
final List<PageImpl> pages = new ArrayList<>();
|
||||
final Router routes = new Router();
|
||||
private boolean isClosedOrClosing;
|
||||
final Map<String, Page.Binding> bindings = new HashMap<>();
|
||||
final Map<String, BindingCallback> bindings = new HashMap<>();
|
||||
PageImpl ownerPage;
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
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 +63,52 @@ 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(WaitForPageOptions options, Runnable code) {
|
||||
if (options == null) {
|
||||
options = new WaitForPageOptions();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
withLogging("BrowserContext.close", () -> closeImpl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cookie> cookies(String url) {
|
||||
return cookies(url == null ? emptyList() : asList(url));
|
||||
}
|
||||
|
||||
private void closeImpl() {
|
||||
if (isClosedOrClosing) {
|
||||
return;
|
||||
}
|
||||
@@ -79,19 +123,33 @@ 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);
|
||||
public void addCookies(List<Cookie> cookies) {
|
||||
withLogging("BrowserContext.addCookies", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("cookies", gson().toJsonTree(cookies));
|
||||
sendMessage("addCookies", params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInitScript(String script, Object arg) {
|
||||
// TODO: serialize arg
|
||||
public void addInitScript(String script) {
|
||||
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInitScript(Path path) {
|
||||
withLogging("BrowserContext.addInitScript", () -> {
|
||||
try {
|
||||
byte[] bytes = readAllBytes(path);
|
||||
addInitScriptImpl(new String(bytes, UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read script from file", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addInitScriptImpl(String script) {
|
||||
JsonObject params = new JsonObject();
|
||||
if (isFunctionBody(script)) {
|
||||
script = "(" + script + ")()";
|
||||
}
|
||||
params.addProperty("source", script);
|
||||
sendMessage("addInitScript", params);
|
||||
}
|
||||
@@ -103,28 +161,36 @@ 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();
|
||||
urls = emptyList();
|
||||
}
|
||||
params.add("urls", gson().toJsonTree(urls));
|
||||
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
|
||||
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
|
||||
return Arrays.asList(cookies);
|
||||
return asList(cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
|
||||
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
|
||||
}
|
||||
|
||||
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
if (bindings.containsKey(name)) {
|
||||
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
|
||||
}
|
||||
@@ -144,17 +210,22 @@ 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));
|
||||
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
|
||||
withLogging("BrowserContext.exposeFunction",
|
||||
() -> exposeBindingImpl(name, (BindingCallback.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();
|
||||
}
|
||||
if (permissions == null) {
|
||||
permissions = Collections.emptyList();
|
||||
permissions = emptyList();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.add("permissions", gson().toJsonTree(permissions));
|
||||
@@ -163,6 +234,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 +266,82 @@ 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());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to write storage state to file", e);
|
||||
public String storageState(StorageStateOptions options) {
|
||||
return withLogging("BrowserContext.storageState", () -> {
|
||||
JsonElement json = sendMessage("storageState");
|
||||
String storageState = json.toString();
|
||||
if (options != null && options.path != null) {
|
||||
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
|
||||
}
|
||||
}
|
||||
return storageState;
|
||||
return storageState;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,24 +359,30 @@ 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void pause() {
|
||||
sendMessage("pause");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -303,7 +391,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
boolean handled = routes.handle(route);
|
||||
if (!handled) {
|
||||
route.continue_();
|
||||
route.resume();
|
||||
}
|
||||
} else if ("page".equals(event)) {
|
||||
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
|
||||
@@ -311,16 +399,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
pages.add(page);
|
||||
} else if ("bindingCall".equals(event)) {
|
||||
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
|
||||
Page.Binding binding = bindings.get(bindingCall.name());
|
||||
BindingCallback binding = bindings.get(bindingCall.name());
|
||||
if (binding != null) {
|
||||
bindingCall.call(binding);
|
||||
}
|
||||
} else if ("close".equals(event)) {
|
||||
isClosedOrClosing = true;
|
||||
if (browser != null) {
|
||||
browser.contexts.remove(this);
|
||||
}
|
||||
listeners.notify(EventType.CLOSE, null);
|
||||
didClose();
|
||||
}
|
||||
}
|
||||
|
||||
void didClose() {
|
||||
isClosedOrClosing = true;
|
||||
if (browser != null) {
|
||||
browser.contexts.remove(this);
|
||||
}
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,43 +16,66 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.Browser;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
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;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
|
||||
class BrowserImpl extends ChannelOwner implements Browser {
|
||||
final Set<BrowserContext> contexts = new HashSet<>();
|
||||
final Set<BrowserContextImpl> contexts = new HashSet<>();
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
public boolean isRemote;
|
||||
private boolean isConnected = true;
|
||||
|
||||
enum EventType {
|
||||
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() {
|
||||
if (isRemote) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to close browser connection", e);
|
||||
}
|
||||
notifyRemoteClosed();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sendMessage("close");
|
||||
} catch (PlaywrightException e) {
|
||||
@@ -62,6 +85,17 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
}
|
||||
|
||||
void notifyRemoteClosed() {
|
||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||
for (BrowserContextImpl context : new ArrayList<>(contexts)) {
|
||||
for (PageImpl page : new ArrayList<>(context.pages)) {
|
||||
page.didClose();
|
||||
}
|
||||
context.didClose();
|
||||
}
|
||||
didClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BrowserContext> contexts() {
|
||||
return new ArrayList<>(contexts);
|
||||
@@ -74,30 +108,81 @@ 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();
|
||||
}
|
||||
if (options.storageStatePath != null) {
|
||||
try (FileReader reader = new FileReader(options.storageStatePath.toFile())) {
|
||||
options.storageState = gson().fromJson(reader, BrowserContext.StorageState.class);
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(options.storageStatePath);
|
||||
options.storageState = new String(bytes, StandardCharsets.UTF_8);
|
||||
options.storageStatePath = null;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read storage state from file", e);
|
||||
}
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (options.extraHTTPHeaders != null) {
|
||||
params.remove("extraHTTPHeaders");
|
||||
params.add("extraHTTPHeaders", Serialization.toProtocol(options.extraHTTPHeaders));
|
||||
JsonObject storageState = null;
|
||||
if (options.storageState != null) {
|
||||
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
|
||||
options.storageState = null;
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (storageState != null) {
|
||||
params.add("storageState", storageState);
|
||||
}
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
recordVideo.addProperty("dir", options.recordVideoDir.toString());
|
||||
if (options.recordVideoSize != null) {
|
||||
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
|
||||
}
|
||||
params.remove("recordVideoDir");
|
||||
params.remove("recordVideoSize");
|
||||
params.add("recordVideo", recordVideo);
|
||||
} else if (options.recordVideoSize != null) {
|
||||
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
|
||||
}
|
||||
if (options.viewportSize != null) {
|
||||
if (options.viewportSize.isPresent()) {
|
||||
JsonElement size = params.get("viewportSize");
|
||||
params.remove("viewportSize");
|
||||
params.add("viewport", size);
|
||||
} else {
|
||||
params.remove("viewportSize");
|
||||
params.addProperty("noDefaultViewport", true);
|
||||
}
|
||||
}
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
JsonElement result = sendMessage("newContext", params);
|
||||
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
}
|
||||
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;
|
||||
@@ -121,8 +206,12 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
@Override
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
if ("close".equals(event)) {
|
||||
isConnected = false;
|
||||
listeners.notify(EventType.DISCONNECTED, null);
|
||||
didClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void didClose() {
|
||||
isConnected = false;
|
||||
listeners.notify(EventType.DISCONNECTED, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,16 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Browser;
|
||||
import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
@@ -32,6 +38,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();
|
||||
}
|
||||
@@ -40,24 +50,104 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Browser connect(String wsEndpoint, ConnectOptions options) {
|
||||
return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
|
||||
}
|
||||
|
||||
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
|
||||
try {
|
||||
Duration timeout = Duration.ofDays(1);
|
||||
if (options != null && options.timeout != null) {
|
||||
timeout = Duration.ofMillis(Math.round(options.timeout));
|
||||
}
|
||||
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), timeout);
|
||||
Connection connection = new Connection(transport);
|
||||
RemoteBrowser remoteBrowser = (RemoteBrowser) connection.waitForObjectWithKnownName("remoteBrowser");
|
||||
PlaywrightImpl playwright = this.connection.getExistingObject("Playwright");
|
||||
SelectorsImpl selectors = remoteBrowser.selectors();
|
||||
playwright.sharedSelectors.addChannel(selectors);
|
||||
BrowserImpl browser = remoteBrowser.browser();
|
||||
browser.isRemote = true;
|
||||
Consumer<WebSocketTransport> connectionCloseListener = new Consumer<WebSocketTransport>() {
|
||||
@Override
|
||||
public void accept(WebSocketTransport t) {
|
||||
browser.notifyRemoteClosed();
|
||||
}
|
||||
};
|
||||
transport.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
playwright.sharedSelectors.removeChannel(selectors);
|
||||
transport.offClose(connectionCloseListener);
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String executablePath() {
|
||||
return initializer.get("executablePath").getAsString();
|
||||
}
|
||||
|
||||
|
||||
@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());
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
recordVideo.addProperty("dir", options.recordVideoDir.toString());
|
||||
if (options.recordVideoSize != null) {
|
||||
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
|
||||
}
|
||||
params.remove("recordVideoDir");
|
||||
params.remove("recordVideoSize");
|
||||
params.add("recordVideo", recordVideo);
|
||||
} else if (options.recordVideoSize != null) {
|
||||
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
|
||||
}
|
||||
if (options.viewportSize != null) {
|
||||
if (options.viewportSize.isPresent()) {
|
||||
JsonElement size = params.get("viewportSize");
|
||||
params.remove("viewportSize");
|
||||
params.add("viewport", size);
|
||||
} else {
|
||||
params.remove("viewportSize");
|
||||
params.addProperty("noDefaultViewport", true);
|
||||
}
|
||||
}
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
|
||||
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -16,13 +16,22 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -56,6 +65,7 @@ public class Connection {
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
private final Root root;
|
||||
private int lastId = 0;
|
||||
private final Path srcDir;
|
||||
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
|
||||
|
||||
class Root extends ChannelOwner {
|
||||
@@ -64,9 +74,18 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
public Connection(InputStream in, OutputStream out) {
|
||||
transport = new Transport(in, out);
|
||||
Connection(Transport transport) {
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
if (srcRoot == null) {
|
||||
srcDir = null;
|
||||
} else {
|
||||
srcDir = Paths.get(srcRoot);
|
||||
if (!Files.exists(srcDir)) {
|
||||
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
@@ -74,15 +93,50 @@ 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 String sourceFile(StackTraceElement frame) {
|
||||
String pkg = frame.getClassName();
|
||||
int lastDot = pkg.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
pkg = "";
|
||||
} else {
|
||||
pkg = frame.getClassName().substring(0, lastDot + 1);
|
||||
}
|
||||
pkg = pkg.replace('.', File.separatorChar);
|
||||
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
|
||||
}
|
||||
|
||||
private JsonArray currentStackTrace() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
|
||||
int index = 0;
|
||||
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
|
||||
index++;
|
||||
};
|
||||
// Find Playwright API call
|
||||
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
|
||||
// hack for tests
|
||||
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
JsonArray jsonStack = new JsonArray();
|
||||
for (; index < stack.length; index++) {
|
||||
StackTraceElement frame = stack[index];
|
||||
JsonObject jsonFrame = new JsonObject();
|
||||
jsonFrame.addProperty("file", sourceFile(frame));
|
||||
jsonFrame.addProperty("line", frame.getLineNumber());
|
||||
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
|
||||
jsonStack.add(jsonFrame);
|
||||
}
|
||||
return jsonStack;
|
||||
}
|
||||
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
|
||||
@@ -94,6 +148,11 @@ public class Connection {
|
||||
message.addProperty("guid", guid);
|
||||
message.addProperty("method", method);
|
||||
message.add("params", params);
|
||||
if (srcDir != null) {
|
||||
JsonObject metadata = new JsonObject();
|
||||
metadata.add("stack", currentStackTrace());
|
||||
message.add("metadata", metadata);
|
||||
}
|
||||
transport.send(gson().toJson(message));
|
||||
return result;
|
||||
}
|
||||
@@ -142,10 +201,12 @@ public class Connection {
|
||||
if (message.error == null) {
|
||||
callback.complete(message.result);
|
||||
} else {
|
||||
if (message.error.error != null) {
|
||||
callback.completeExceptionally(new ServerException(message.error.error));
|
||||
} else {
|
||||
if (message.error.error == null) {
|
||||
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
|
||||
} else if ("TimeoutError".equals(message.error.error.name)) {
|
||||
callback.completeExceptionally(new TimeoutError(message.error.error.toString()));
|
||||
} else {
|
||||
callback.completeExceptionally(new DriverException(message.error.error));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -236,6 +297,9 @@ public class Connection {
|
||||
case "Request":
|
||||
result = new RequestImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "RemoteBrowser":
|
||||
result = new RemoteBrowser(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Response":
|
||||
result = new ResponseImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
|
||||
@@ -48,7 +48,11 @@ public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Location location() {
|
||||
return gson().fromJson(initializer.get("location"), Location.class);
|
||||
@Override
|
||||
public String location() {
|
||||
JsonObject location = initializer.getAsJsonObject("location");
|
||||
return location.get("url").getAsString() + ":" +
|
||||
location.get("lineNumber").getAsNumber() + ":" +
|
||||
location.get("columnNumber").getAsNumber();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.DeviceDescriptor;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
class DeviceDescriptorImpl implements DeviceDescriptor {
|
||||
PlaywrightImpl playwright;
|
||||
private static class ViewportImpl implements Viewport{
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
private ViewportImpl viewport;
|
||||
private String userAgent;
|
||||
private int deviceScaleFactor;
|
||||
private boolean isMobile;
|
||||
private boolean hasTouch;
|
||||
private String defaultBrowserType;
|
||||
|
||||
@Override
|
||||
public Viewport viewport() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deviceScaleFactor() {
|
||||
return deviceScaleFactor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMobile() {
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTouch() {
|
||||
return hasTouch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserType defaultBrowserType() {
|
||||
switch (defaultBrowserType) {
|
||||
case "chromium": return playwright.chromium();
|
||||
case "firefox": return playwright.firefox();
|
||||
case "webkit": return playwright.webkit();
|
||||
default: throw new PlaywrightException("Unknown type: " + defaultBrowserType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,27 +18,26 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
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
|
||||
@@ -52,17 +51,7 @@ public class DialogImpl extends ChannelOwner implements Dialog {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type type() {
|
||||
switch (initializer.get("type").getAsString()) {
|
||||
case "alert": return Type.ALERT;
|
||||
case "beforeunload": return Type.BEFOREUNLOAD;
|
||||
case "confirm": return Type.CONFIRM;
|
||||
case "prompt": return Type.PROMPT;
|
||||
default: throw new PlaywrightException("Unexpected dialog type: " + initializer.get("type").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isHandled() {
|
||||
return handled;
|
||||
public String type() {
|
||||
return initializer.get("type").getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,27 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Download;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.writeToFile;
|
||||
|
||||
public class DownloadImpl extends ChannelOwner implements Download {
|
||||
private final BrowserImpl browser;
|
||||
|
||||
public DownloadImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
browser = ((BrowserContextImpl) parent).browser();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,38 +51,58 @@ 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", () -> {
|
||||
if (browser != null && browser.isRemote) {
|
||||
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
|
||||
}
|
||||
JsonObject json = sendMessage("path").getAsJsonObject();
|
||||
return FileSystems.getDefault().getPath(json.get("value").getAsString());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAs(Path path) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("path", path.toString());
|
||||
sendMessage("saveAs", params);
|
||||
withLogging("Download.saveAs", () -> {
|
||||
if (browser != null && browser.isRemote) {
|
||||
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
|
||||
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
|
||||
writeToFile(stream.stream(), path);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("path", path.toString());
|
||||
sendMessage("saveAs", params);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+12
-14
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -14,17 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
public interface DeviceDescriptor {
|
||||
interface Viewport {
|
||||
int width();
|
||||
int height();
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
class DriverException extends PlaywrightException {
|
||||
DriverException(SerializedError.Error error) {
|
||||
super(error.toString());
|
||||
}
|
||||
Viewport viewport();
|
||||
String userAgent();
|
||||
int deviceScaleFactor();
|
||||
boolean isMobile();
|
||||
boolean hasTouch();
|
||||
BrowserType defaultBrowserType();
|
||||
}
|
||||
@@ -16,23 +16,26 @@
|
||||
|
||||
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 com.microsoft.playwright.options.BoundingBox;
|
||||
import com.microsoft.playwright.options.ElementState;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.SelectOption;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
|
||||
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -46,67 +49,79 @@ 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.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.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,25 +131,23 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("button");
|
||||
if (options.button != null) {
|
||||
params.addProperty("button", Serialization.toProtocol(options.button));
|
||||
}
|
||||
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
|
||||
sendMessage("click", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame contentFrame() {
|
||||
return withLogging("ElementHandle.contentFrame", () -> contentFrameImpl());
|
||||
}
|
||||
|
||||
private Frame contentFrameImpl() {
|
||||
JsonObject json = sendMessage("contentFrame").getAsJsonObject();
|
||||
if (!json.has("frame")) {
|
||||
return null;
|
||||
@@ -144,33 +157,33 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("button");
|
||||
if (options.button != null) {
|
||||
params.addProperty("button", Serialization.toProtocol(options.button));
|
||||
}
|
||||
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
|
||||
sendMessage("dblclick", params);
|
||||
}
|
||||
|
||||
@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 +194,107 @@ 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();
|
||||
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();
|
||||
}
|
||||
@@ -233,31 +303,29 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
sendMessage("press", params);
|
||||
}
|
||||
|
||||
private static String toProtocol(ScreenshotOptions.Type type) {
|
||||
return type.toString().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] screenshot(ScreenshotOptions options) {
|
||||
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
|
||||
}
|
||||
|
||||
private byte[] screenshotImpl(ScreenshotOptions options) {
|
||||
if (options == null) {
|
||||
options = new ScreenshotOptions();
|
||||
}
|
||||
if (options.type == null) {
|
||||
options.type = ScreenshotOptions.Type.PNG;
|
||||
options.type = PNG;
|
||||
if (options.path != null) {
|
||||
String fileName = options.path.getFileName().toString();
|
||||
int extStart = fileName.lastIndexOf('.');
|
||||
if (extStart != -1) {
|
||||
String extension = fileName.substring(extStart).toLowerCase();
|
||||
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
|
||||
options.type = ScreenshotOptions.Type.JPEG;
|
||||
options.type = JPEG;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("type");
|
||||
params.addProperty("type", toProtocol(options.type));
|
||||
params.remove("path");
|
||||
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
|
||||
|
||||
@@ -270,6 +338,37 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
|
||||
@Override
|
||||
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
|
||||
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String value, SelectOptionOptions options) {
|
||||
String[] values = value == null ? null : new String[]{ value };
|
||||
return selectOption(values, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(ElementHandle value, SelectOptionOptions options) {
|
||||
ElementHandle[] values = value == null ? null : new ElementHandle[]{ value };
|
||||
return selectOption(values, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String[] values, SelectOptionOptions options) {
|
||||
if (values == null) {
|
||||
return selectOption(new SelectOption[0], options);
|
||||
}
|
||||
return selectOption(Arrays.asList(values).stream().map(
|
||||
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(SelectOption value, SelectOptionOptions options) {
|
||||
SelectOption[] values = value == null ? null : new SelectOption[]{ value };
|
||||
return selectOption(values, options);
|
||||
}
|
||||
|
||||
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
|
||||
if (options == null) {
|
||||
options = new ScrollIntoViewIfNeededOptions();
|
||||
}
|
||||
@@ -302,12 +401,23 @@ 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("SelectOption", () -> {
|
||||
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
|
||||
return parseStringList(json.getAsJsonArray("values"));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectText(SelectTextOptions options) {
|
||||
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path files, SetInputFilesOptions options) {
|
||||
setInputFiles(new Path[]{files}, options);
|
||||
}
|
||||
|
||||
private void selectTextImpl(SelectTextOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectTextOptions();
|
||||
}
|
||||
@@ -321,7 +431,16 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
|
||||
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
|
||||
setInputFiles(new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
@@ -332,17 +451,35 @@ 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();
|
||||
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 +490,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,48 +502,42 @@ 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();
|
||||
}
|
||||
if (state == null) {
|
||||
throw new IllegalArgumentException("State cannot be null");
|
||||
}
|
||||
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) {
|
||||
if (state == null) {
|
||||
throw new IllegalArgumentException("State cannot by null");
|
||||
}
|
||||
return state.toString().toLowerCase();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
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;
|
||||
}
|
||||
|
||||
private static String toProtocol(WaitForSelectorOptions.State state) {
|
||||
if (state == null) {
|
||||
state = WaitForSelectorOptions.State.VISIBLE;
|
||||
}
|
||||
return state.toString().toLowerCase();
|
||||
return connection.getExistingObject(element.get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,21 @@
|
||||
|
||||
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 com.microsoft.playwright.options.FilePayload;
|
||||
|
||||
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;
|
||||
@@ -53,13 +51,24 @@ class FileChooserImpl implements FileChooser {
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFiles(Path files, SetFilesOptions options) {
|
||||
setFiles(new Path[]{files}, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFiles(Path[] files, SetFilesOptions options) {
|
||||
setFiles(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFiles(FilePayload files, SetFilesOptions options) {
|
||||
setFiles(new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFiles(FilePayload[] files, SetFilesOptions options) {
|
||||
element.setInputFiles(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class));
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
|
||||
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 com.microsoft.playwright.options.*;
|
||||
|
||||
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.options.LoadState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
public class FrameImpl extends ChannelOwner implements Frame {
|
||||
private String name;
|
||||
@@ -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,37 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public List<ElementHandle> querySelectorAll(String selector) {
|
||||
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, String value, SelectOptionOptions options) {
|
||||
String[] values = value == null ? null : new String[]{ value };
|
||||
return selectOption(selector, values, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, ElementHandle value, SelectOptionOptions options) {
|
||||
ElementHandle[] values = value == null ? null : new ElementHandle[]{value};
|
||||
return selectOption(selector, values, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
|
||||
if (values == null) {
|
||||
return selectOption(selector, new SelectOption[0], options);
|
||||
}
|
||||
return selectOption(selector, Arrays.asList(values).stream().map(
|
||||
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, SelectOption value, SelectOptionOptions options) {
|
||||
SelectOption[] values = value == null ? null : new SelectOption[]{value};
|
||||
return selectOption(selector, values, options);
|
||||
}
|
||||
|
||||
List<ElementHandle> querySelectorAllImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
JsonElement json = sendMessage("querySelectorAll", params);
|
||||
@@ -97,10 +132,13 @@ 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);
|
||||
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);
|
||||
@@ -109,10 +147,13 @@ 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);
|
||||
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);
|
||||
@@ -120,13 +161,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 +180,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 +206,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,53 +233,47 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
|
||||
params.remove("button");
|
||||
if (options.button != null) {
|
||||
params.addProperty("button", Serialization.toProtocol(options.button));
|
||||
}
|
||||
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
|
||||
sendMessage("click", params);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
|
||||
params.remove("button");
|
||||
if (options.button != null) {
|
||||
params.addProperty("button", Serialization.toProtocol(options.button));
|
||||
}
|
||||
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
|
||||
sendMessage("dblclick", params);
|
||||
}
|
||||
|
||||
@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,10 +286,13 @@ 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");
|
||||
params.addProperty("isFunction", isFunctionBody(expression));
|
||||
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
|
||||
JsonElement json = sendMessage("evaluateExpression", params);
|
||||
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
|
||||
@@ -251,10 +301,13 @@ 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");
|
||||
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());
|
||||
@@ -262,6 +315,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 +330,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 +344,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,15 +374,15 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("url", url);
|
||||
if (options.waitUntil != null) {
|
||||
params.remove("waitUntil");
|
||||
params.addProperty("waitUntil", toProtocol(options.waitUntil));
|
||||
}
|
||||
JsonElement result = sendMessage("goto", params);
|
||||
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
|
||||
if (jsonResponse == null) {
|
||||
@@ -323,6 +393,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 +407,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 +422,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 +435,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 +547,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();
|
||||
}
|
||||
@@ -385,7 +561,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
|
||||
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
|
||||
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
|
||||
}
|
||||
|
||||
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
@@ -399,6 +579,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();
|
||||
}
|
||||
@@ -415,37 +599,45 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return parseStringList(json.getAsJsonArray("values"));
|
||||
}
|
||||
|
||||
static String toProtocol(LoadState waitUntil) {
|
||||
if (waitUntil == null) {
|
||||
waitUntil = LoadState.LOAD;
|
||||
}
|
||||
switch (waitUntil) {
|
||||
case DOMCONTENTLOADED: return "domcontentloaded";
|
||||
case LOAD: return "load";
|
||||
case NETWORKIDLE: return "networkidle";
|
||||
default: throw new PlaywrightException("Unexpected value: " + waitUntil);
|
||||
}
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Frame.setContent", () -> setContentImpl(html, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
public void setInputFiles(String selector, Path files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, new Path[] {files}, options);
|
||||
}
|
||||
|
||||
void setContentImpl(String html, SetContentOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetContentOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("html", html);
|
||||
params.remove("waitUntil");
|
||||
params.addProperty("waitUntil", toProtocol(options.waitUntil));
|
||||
sendMessage("setContent", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
@@ -457,20 +649,23 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("tap", params);
|
||||
}
|
||||
|
||||
@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 +676,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 +701,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,23 +719,28 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
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 +752,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 +768,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 +789,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 +804,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,51 +855,53 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deferred<Response> waitForNavigation(WaitForNavigationOptions options) {
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
|
||||
}
|
||||
|
||||
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForNavigationOptions();
|
||||
}
|
||||
if (options.waitUntil == null) {
|
||||
options.waitUntil = LOAD;
|
||||
options.waitUntil = WaitUntilState.LOAD;
|
||||
}
|
||||
|
||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(options.glob, options.pattern, options.predicate);
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(options.url);
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||
return toDeferred(new WaitableRace<>(waitables));
|
||||
}
|
||||
|
||||
private static String toProtocol(WaitForSelectorOptions.State state) {
|
||||
return state.toString().toLowerCase();
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
if (options.state != null) {
|
||||
params.remove("state");
|
||||
params.addProperty("state", toProtocol(options.state));
|
||||
JsonElement json = sendMessage("waitForSelector", params);
|
||||
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
|
||||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
@@ -26,11 +25,13 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||
|
||||
public class JSHandleImpl extends ChannelOwner implements JSHandle {
|
||||
private String preview;
|
||||
|
||||
public JSHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this.preview = initializer.get("preview").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,56 +41,77 @@ 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.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.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,62 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
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 DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("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 = ZonedDateTime.now().format(timestampFormat);
|
||||
System.err.println(timestamp + " pw:api " + message);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,10 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Mouse;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Serialization.toProtocol;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
class MouseImpl implements Mouse {
|
||||
@@ -32,22 +30,26 @@ 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();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("x", x);
|
||||
params.addProperty("y", y);
|
||||
if (options.button != null) {
|
||||
params.remove("button");
|
||||
params.addProperty("button", toProtocol(options.button));
|
||||
}
|
||||
page.sendMessage("mouseClick", params);
|
||||
}
|
||||
|
||||
@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 +62,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 +74,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 +90,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
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PipeTransport implements Transport {
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
|
||||
|
||||
private final ReaderThread readerThread;
|
||||
private final WriterThread writerThread;
|
||||
|
||||
private boolean isClosed;
|
||||
|
||||
PipeTransport(InputStream input, OutputStream output) {
|
||||
DataInputStream in = new DataInputStream(new BufferedInputStream(input));
|
||||
readerThread = new ReaderThread(in, incoming);
|
||||
readerThread.start();
|
||||
writerThread = new WriterThread(output, outgoing);
|
||||
writerThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
outgoing.put(message);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String poll(Duration timeout) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to read message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
// We interrupt only the outgoing pipe and keep reader thread running as
|
||||
// otherwise child process may block on writing to its stdout and never
|
||||
// exit (observed on Windows).
|
||||
readerThread.isClosing = true;
|
||||
writerThread.out.close();
|
||||
writerThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
class ReaderThread extends Thread {
|
||||
private final DataInputStream in;
|
||||
private final BlockingQueue<String> queue;
|
||||
volatile boolean isClosing;
|
||||
|
||||
private static int readIntLE(DataInputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
int ch4 = in.read();
|
||||
if ((ch1 | ch2 | ch3 | ch4) < 0) {
|
||||
throw new EOFException();
|
||||
} else {
|
||||
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
|
||||
}
|
||||
}
|
||||
|
||||
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
|
||||
this.in = in;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
queue.put(readMessage());
|
||||
} catch (IOException e) {
|
||||
if (!isInterrupted() && !isClosing) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String readMessage() throws IOException {
|
||||
int len = readIntLE(in);
|
||||
byte[] raw = new byte[len];
|
||||
in.readFully(raw, 0, len);
|
||||
return new String(raw, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
class WriterThread extends Thread {
|
||||
final OutputStream out;
|
||||
private final BlockingQueue<String> queue;
|
||||
|
||||
private static void writeIntLE(OutputStream out, int v) throws IOException {
|
||||
out.write(v >>> 0 & 255);
|
||||
out.write(v >>> 8 & 255);
|
||||
out.write(v >>> 16 & 255);
|
||||
out.write(v >>> 24 & 255);
|
||||
}
|
||||
|
||||
WriterThread(OutputStream out, BlockingQueue<String> queue) {
|
||||
this.out = out;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
if (queue.isEmpty())
|
||||
out.flush();
|
||||
sendMessage(queue.take());
|
||||
} catch (IOException e) {
|
||||
if (!isInterrupted())
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(String message) throws IOException {
|
||||
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
writeIntLE(out, bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,13 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.DeviceDescriptor;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Selectors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
@@ -40,7 +35,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
// pb.environment().put("DEBUG", "pw:pro*");
|
||||
Process p = pb.start();
|
||||
Connection connection = new Connection(p.getInputStream(), p.getOutputStream());
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
|
||||
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
|
||||
result.driverProcess = p;
|
||||
return result;
|
||||
@@ -52,24 +47,15 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private final BrowserTypeImpl chromium;
|
||||
private final BrowserTypeImpl firefox;
|
||||
private final BrowserTypeImpl webkit;
|
||||
private final Selectors selectors;
|
||||
private final Map<String, DeviceDescriptor> devices = new HashMap<>();
|
||||
final SharedSelectors sharedSelectors = new SharedSelectors();;
|
||||
|
||||
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
|
||||
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
|
||||
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
|
||||
selectors = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
|
||||
Gson gson = Serialization.gson();
|
||||
for (JsonElement item : initializer.getAsJsonArray("deviceDescriptors")) {
|
||||
JsonObject o = item.getAsJsonObject();
|
||||
String name = o.get("name").getAsString();
|
||||
DeviceDescriptorImpl descriptor = gson.fromJson(o.get("descriptor"), DeviceDescriptorImpl.class);
|
||||
descriptor.playwright = this;
|
||||
devices.put(name, descriptor);
|
||||
}
|
||||
SelectorsImpl channel = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
sharedSelectors.addChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,23 +73,25 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
return webkit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, DeviceDescriptor> devices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selectors selectors() {
|
||||
return selectors;
|
||||
return sharedSelectors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
connection.close();
|
||||
// playwright-cli will exit when its stdin is closed, we wait for that.
|
||||
boolean didClose = driverProcess.waitFor(30, TimeUnit.SECONDS);
|
||||
if (!didClose) {
|
||||
System.err.println("WARNING: Timed out while waiting for driver process to exit");
|
||||
public void close() {
|
||||
try {
|
||||
connection.close();
|
||||
// playwright-cli will exit when its stdin is closed, we wait for that.
|
||||
boolean didClose = driverProcess.waitFor(30, TimeUnit.SECONDS);
|
||||
if (!didClose) {
|
||||
System.err.println("WARNING: Timed out while waiting for driver process to exit");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to terminate", e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new PlaywrightException("Operation interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class RemoteBrowser extends ChannelOwner {
|
||||
RemoteBrowser(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
BrowserImpl browser() {
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
|
||||
}
|
||||
|
||||
SelectorsImpl selectors() {
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Response;
|
||||
import com.microsoft.playwright.options.Timing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
@@ -32,7 +33,8 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
private RequestImpl redirectedFrom;
|
||||
private RequestImpl redirectedTo;
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
RequestFailure failure;
|
||||
String failure;
|
||||
Timing timing;
|
||||
|
||||
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -53,7 +55,7 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestFailure failure() {
|
||||
public String failure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
@@ -107,16 +109,18 @@ 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
|
||||
public RequestTiming timing() {
|
||||
return null;
|
||||
public Timing timing() {
|
||||
return timing;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Response;
|
||||
import com.microsoft.playwright.options.Timing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
@@ -45,21 +46,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"), Timing.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
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -36,72 +35,90 @@ 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 resume(ResumeOptions options) {
|
||||
withLogging("Route.resume", () -> resumeImpl(options));
|
||||
}
|
||||
|
||||
private void resumeImpl(ResumeOptions options) {
|
||||
if (options == null) {
|
||||
options = new ResumeOptions();
|
||||
}
|
||||
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) {
|
||||
byte[] bytes = null;
|
||||
if (options.postData instanceof byte[]) {
|
||||
bytes = (byte[]) options.postData;
|
||||
} else if (options.postData instanceof String) {
|
||||
bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName());
|
||||
}
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
sendMessage("continue", params);
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@@ -27,30 +27,18 @@ import java.nio.file.Path;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class SelectorsImpl extends ChannelOwner implements Selectors {
|
||||
class SelectorsImpl extends ChannelOwner {
|
||||
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String name, String script, RegisterOptions options) {
|
||||
void registerImpl(String name, String script, Selectors.RegisterOptions options) {
|
||||
if (options == null) {
|
||||
options = new RegisterOptions();
|
||||
options = new Selectors.RegisterOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("name", name);
|
||||
params.addProperty("source", script);
|
||||
sendMessage("register", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String name, Path path, RegisterOptions options) {
|
||||
byte[] buffer;
|
||||
try {
|
||||
buffer = Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read selector from file: " + path, e);
|
||||
}
|
||||
register(name, new String(buffer, UTF_8), options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.gson.*;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -36,11 +37,18 @@ class Serialization {
|
||||
static Gson gson() {
|
||||
if (gson == null) {
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(BrowserContext.SameSite.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
|
||||
.registerTypeAdapter(Page.EmulateMediaParams.Media.class, new MediaSerializer())
|
||||
.registerTypeAdapter(Media.class, new MediaSerializer())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
|
||||
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
|
||||
.registerTypeHierarchyAdapter(Map.class, new StringMapSerializer())
|
||||
.registerTypeAdapter(Path.class, new PathSerializer()).create();
|
||||
}
|
||||
return gson;
|
||||
@@ -179,39 +187,29 @@ class Serialization {
|
||||
throw new PlaywrightException("Unexpected result: " + gson().toJson(value));
|
||||
}
|
||||
|
||||
static String toProtocol(Mouse.Button button) {
|
||||
switch (button) {
|
||||
case LEFT:
|
||||
return "left";
|
||||
case RIGHT:
|
||||
return "right";
|
||||
case MIDDLE:
|
||||
return "middle";
|
||||
default:
|
||||
throw new PlaywrightException("Unexpected value: " + button);
|
||||
private static class KeyboardModifiersSerializer implements JsonSerializer<List<KeyboardModifier>> {
|
||||
@Override
|
||||
public JsonArray serialize(List<KeyboardModifier> modifiers, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonArray result = new JsonArray();
|
||||
if (modifiers.contains(KeyboardModifier.ALT)) {
|
||||
result.add("Alt");
|
||||
}
|
||||
if (modifiers.contains(KeyboardModifier.CONTROL)) {
|
||||
result.add("Control");
|
||||
}
|
||||
if (modifiers.contains(KeyboardModifier.META)) {
|
||||
result.add("Meta");
|
||||
}
|
||||
if (modifiers.contains(KeyboardModifier.SHIFT)) {
|
||||
result.add("Shift");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static JsonArray toProtocol(Set<Keyboard.Modifier> modifiers) {
|
||||
JsonArray result = new JsonArray();
|
||||
if (modifiers.contains(Keyboard.Modifier.ALT)) {
|
||||
result.add("Alt");
|
||||
}
|
||||
if (modifiers.contains(Keyboard.Modifier.CONTROL)) {
|
||||
result.add("Control");
|
||||
}
|
||||
if (modifiers.contains(Keyboard.Modifier.META)) {
|
||||
result.add("Meta");
|
||||
}
|
||||
if (modifiers.contains(Keyboard.Modifier.SHIFT)) {
|
||||
result.add("Shift");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JsonArray toJsonArray(FileChooser.FilePayload[] files) {
|
||||
static JsonArray toJsonArray(FilePayload[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (FileChooser.FilePayload p : files) {
|
||||
for (FilePayload p : files) {
|
||||
JsonObject jsonFile = new JsonObject();
|
||||
jsonFile.addProperty("name", p.name);
|
||||
jsonFile.addProperty("mimeType", p.mimeType);
|
||||
@@ -252,13 +250,14 @@ class Serialization {
|
||||
|
||||
private static class OptionalSerializer implements JsonSerializer<Optional<?>> {
|
||||
private static boolean isSupported(Type type) {
|
||||
return new TypeToken<Optional<Page.EmulateMediaParams.Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName());
|
||||
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Optional<?> src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
assert isSupported(typeOfSrc);
|
||||
assert isSupported(typeOfSrc) : "Unexpected optional type: " + typeOfSrc.getTypeName();
|
||||
if (!src.isPresent()) {
|
||||
return new JsonPrimitive("null");
|
||||
}
|
||||
@@ -275,9 +274,19 @@ class Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
private static class MediaSerializer implements JsonSerializer<Page.EmulateMediaParams.Media> {
|
||||
private static class StringMapSerializer implements JsonSerializer<Map<String, String>> {
|
||||
@Override
|
||||
public JsonElement serialize(Page.EmulateMediaParams.Media src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
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<Media> {
|
||||
@Override
|
||||
public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -289,9 +298,16 @@ class Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SameSiteAdapter extends TypeAdapter<BrowserContext.SameSite> {
|
||||
private static class ToLowerCaseSerializer<E extends Enum<E>> implements JsonSerializer<E> {
|
||||
@Override
|
||||
public void write(JsonWriter out, BrowserContext.SameSite value) throws IOException {
|
||||
public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {
|
||||
String stringValue;
|
||||
switch (value) {
|
||||
case STRICT:
|
||||
@@ -310,9 +326,9 @@ class Serialization {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserContext.SameSite read(JsonReader in) throws IOException {
|
||||
public SameSiteAttribute read(JsonReader in) throws IOException {
|
||||
String value = in.nextString();
|
||||
return BrowserContext.SameSite.valueOf(value.toUpperCase());
|
||||
return SameSiteAttribute.valueOf(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
class ServerException extends PlaywrightException {
|
||||
private final SerializedError.Error error;
|
||||
|
||||
ServerException(SerializedError.Error error) {
|
||||
super(error.name + ": " + error.message);
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(PrintWriter s) {
|
||||
super.printStackTrace(s);
|
||||
s.println("Caused by Playwright server error:");
|
||||
s.println(getMessage());
|
||||
s.println(error.stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printStackTrace(PrintStream s) {
|
||||
super.printStackTrace(s);
|
||||
s.println("Caused by Playwright server error:");
|
||||
s.println(getMessage());
|
||||
s.println(error.stack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Selectors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class SharedSelectors extends LoggingSupport implements Selectors {
|
||||
private final List<SelectorsImpl> channels = new ArrayList<>();
|
||||
private final List<Registration> registrations = new ArrayList<>();
|
||||
|
||||
private static class Registration {
|
||||
final String name;
|
||||
final String script;
|
||||
final RegisterOptions options;
|
||||
|
||||
Registration(String name, String script, RegisterOptions options) {
|
||||
this.name = name;
|
||||
this.script = script;
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String name, String script, RegisterOptions options) {
|
||||
withLogging("Selectors.register", () -> registerImpl(name, script, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String name, Path path, RegisterOptions options) {
|
||||
withLogging("Selectors.register", () -> {
|
||||
byte[] buffer;
|
||||
try {
|
||||
buffer = Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read selector from file: " + path, e);
|
||||
}
|
||||
registerImpl(name, new String(buffer, UTF_8), options);
|
||||
});
|
||||
}
|
||||
|
||||
void addChannel(SelectorsImpl channel) {
|
||||
registrations.forEach(r -> channel.registerImpl(r.name, r.script, r.options));
|
||||
channels.add(channel);
|
||||
}
|
||||
|
||||
void removeChannel(SelectorsImpl channel) {
|
||||
channels.remove(channel);
|
||||
}
|
||||
|
||||
private void registerImpl(String name, String script, RegisterOptions options) {
|
||||
channels.forEach(impl -> impl.registerImpl(name, script, options));
|
||||
registrations.add(new Registration(name, script, options));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,153 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Transport {
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
|
||||
|
||||
private final ReaderThread readerThread;
|
||||
private final WriterThread writerThread;
|
||||
|
||||
private boolean isClosed;
|
||||
|
||||
Transport(InputStream input, OutputStream output) {
|
||||
DataInputStream in = new DataInputStream(new BufferedInputStream(input));
|
||||
readerThread = new ReaderThread(in, incoming);
|
||||
readerThread.start();
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
writerThread = new WriterThread(out, outgoing);
|
||||
writerThread.start();
|
||||
}
|
||||
|
||||
public void send(String message) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
outgoing.put(message);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String poll(Duration timeout) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to read message", e);
|
||||
}
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
// We interrupt only the outgoing pipe and keep reader thread running as
|
||||
// otherwise child process may block on writing to its stdout and never
|
||||
// exit (observed on Windows).
|
||||
readerThread.isClosing = true;
|
||||
writerThread.out.close();
|
||||
writerThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
class ReaderThread extends Thread {
|
||||
private final DataInputStream in;
|
||||
private final BlockingQueue<String> queue;
|
||||
volatile boolean isClosing;
|
||||
|
||||
private static int readIntLE(DataInputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
int ch4 = in.read();
|
||||
if ((ch1 | ch2 | ch3 | ch4) < 0) {
|
||||
throw new EOFException();
|
||||
} else {
|
||||
return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
|
||||
}
|
||||
}
|
||||
|
||||
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
|
||||
this.in = in;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
queue.put(readMessage());
|
||||
} catch (IOException e) {
|
||||
if (!isInterrupted() && !isClosing) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String readMessage() throws IOException {
|
||||
int len = readIntLE(in);
|
||||
byte[] raw = new byte[len];
|
||||
in.readFully(raw, 0, len);
|
||||
return new String(raw, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
class WriterThread extends Thread {
|
||||
final DataOutputStream out;
|
||||
private final BlockingQueue<String> queue;
|
||||
|
||||
private static void writeIntLE(DataOutputStream 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) {
|
||||
this.out = out;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
if (queue.isEmpty())
|
||||
out.flush();
|
||||
sendMessage(queue.take());
|
||||
} catch (IOException e) {
|
||||
if (!isInterrupted())
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(String message) throws IOException {
|
||||
int len = message.length();
|
||||
writeIntLE(out, len);
|
||||
out.writeBytes(message);
|
||||
}
|
||||
public interface Transport {
|
||||
void send(String message);
|
||||
String poll(Duration timeout);
|
||||
void close() throws IOException;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -34,25 +36,20 @@ class UrlMatcher {
|
||||
return new UrlMatcher(null, null);
|
||||
}
|
||||
|
||||
static UrlMatcher forOneOf(String glob, Pattern pattern, Predicate<String> predicate) {
|
||||
UrlMatcher result = UrlMatcher.any();
|
||||
int conditionCount = 0;
|
||||
if (glob != null) {
|
||||
conditionCount += 1;
|
||||
result = new UrlMatcher(glob);
|
||||
static UrlMatcher forOneOf(Object object) {
|
||||
if (object == null) {
|
||||
return UrlMatcher.any();
|
||||
}
|
||||
if (pattern != null) {
|
||||
conditionCount += 1;
|
||||
result = new UrlMatcher(pattern);
|
||||
if (object instanceof String) {
|
||||
return new UrlMatcher((String) object);
|
||||
}
|
||||
if (predicate != null) {
|
||||
conditionCount += 1;
|
||||
result = new UrlMatcher(predicate);
|
||||
if (object instanceof Pattern) {
|
||||
return new UrlMatcher((Pattern) object);
|
||||
}
|
||||
if (conditionCount > 1) {
|
||||
throw new IllegalArgumentException("Only one of glob, pattern and predicate can be specified");
|
||||
if (object instanceof Predicate) {
|
||||
return new UrlMatcher((Predicate<String>) object);
|
||||
}
|
||||
return result;
|
||||
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
|
||||
}
|
||||
|
||||
UrlMatcher(String url) {
|
||||
|
||||
@@ -18,13 +18,13 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.microsoft.playwright.FileChooser;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -114,8 +114,8 @@ class Utils {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
static FileChooser.FilePayload[] toFilePayloads(Path[] files) {
|
||||
List<FileChooser.FilePayload> payloads = new ArrayList<>();
|
||||
static FilePayload[] toFilePayloads(Path[] files) {
|
||||
List<FilePayload> payloads = new ArrayList<>();
|
||||
for (Path file : files) {
|
||||
byte[] buffer;
|
||||
try {
|
||||
@@ -123,13 +123,13 @@ class Utils {
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file", e);
|
||||
}
|
||||
payloads.add(new FileChooser.FilePayload(file.getFileName().toString(), mimeType(file), buffer));
|
||||
payloads.add(new FilePayload(file.getFileName().toString(), mimeType(file), buffer));
|
||||
}
|
||||
return payloads.toArray(new FileChooser.FilePayload[0]);
|
||||
return payloads.toArray(new FilePayload[0]);
|
||||
}
|
||||
|
||||
static void writeToFile(byte[] buffer, Path path) {
|
||||
Path dir = path.getParent();
|
||||
static void mkParentDirs(Path file) {
|
||||
Path dir = file.getParent();
|
||||
if (dir != null) {
|
||||
if (!Files.exists(dir)) {
|
||||
try {
|
||||
@@ -139,13 +139,30 @@ class Utils {
|
||||
}
|
||||
}
|
||||
}
|
||||
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(path.toFile()));) {
|
||||
}
|
||||
|
||||
static void writeToFile(byte[] buffer, Path path) {
|
||||
mkParentDirs(path);
|
||||
try (FileOutputStream out = new FileOutputStream(path.toFile())) {
|
||||
out.write(buffer);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to write to file", e);
|
||||
}
|
||||
}
|
||||
|
||||
static void writeToFile(InputStream inputStream, Path path) {
|
||||
mkParentDirs(path);
|
||||
try (FileOutputStream out = new FileOutputStream(path.toFile())) {
|
||||
byte[] buf = new byte[8192];
|
||||
int length;
|
||||
while ((length = inputStream.read(buf)) > 0) {
|
||||
out.write(buf, 0, length);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to write to file", e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isSafeCloseError(PlaywrightException exception) {
|
||||
return isSafeCloseError(exception.getMessage());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.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;
|
||||
}
|
||||
}
|
||||
@@ -16,23 +16,20 @@
|
||||
|
||||
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 +37,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 +57,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> {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
class WaitableResult<T> implements Waitable<T> {
|
||||
private T result;
|
||||
@@ -47,6 +48,9 @@ class WaitableResult<T> implements Waitable<T> {
|
||||
@Override
|
||||
public T get() {
|
||||
if (exception != null) {
|
||||
if (exception instanceof TimeoutError) {
|
||||
throw new TimeoutError(exception.getMessage(), exception);
|
||||
}
|
||||
throw new PlaywrightException(exception.getMessage(), exception);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
class WaitableTimeout<T> implements Waitable<T> {
|
||||
private final long deadline;
|
||||
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 TimeoutError("Timeout " + timeoutStr + "ms exceeded");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,32 +17,87 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.WebSocket;
|
||||
import com.microsoft.playwright.WebSocketFrame;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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<WebSocketFrame> handler) {
|
||||
listeners.add(EventType.FRAMERECEIVED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offFrameReceived(Consumer<WebSocketFrame> handler) {
|
||||
listeners.remove(EventType.FRAMERECEIVED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(Consumer<WebSocketFrame> handler) {
|
||||
listeners.add(EventType.FRAMESENT, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offFrameSent(Consumer<WebSocketFrame> 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 WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable code) {
|
||||
if (options == null) {
|
||||
options = new WaitForFrameReceivedOptions();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable code) {
|
||||
if (options == null) {
|
||||
options = new WaitForFrameSentOptions();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,64 +110,42 @@ 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 {
|
||||
private static class WebSocketFrameImpl implements WebSocketFrame {
|
||||
private final byte[] bytes;
|
||||
|
||||
FrameDataImpl(String payload, boolean isBase64) {
|
||||
WebSocketFrameImpl(String payload, boolean isBase64) {
|
||||
if (isBase64) {
|
||||
bytes = Base64.getDecoder().decode(payload);
|
||||
} else {
|
||||
@@ -121,7 +154,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] body() {
|
||||
public byte[] binary() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@@ -135,15 +168,15 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
switch (event) {
|
||||
case "frameSent": {
|
||||
FrameDataImpl frameData = new FrameDataImpl(
|
||||
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
|
||||
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
|
||||
listeners.notify(EventType.FRAMESENT, frameData);
|
||||
listeners.notify(EventType.FRAMESENT, WebSocketFrame);
|
||||
break;
|
||||
}
|
||||
case "frameReceived": {
|
||||
FrameDataImpl frameData = new FrameDataImpl(
|
||||
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
|
||||
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
|
||||
listeners.notify(EventType.FRAMERECEIVED, frameData);
|
||||
listeners.notify(EventType.FRAMERECEIVED, WebSocketFrame);
|
||||
break;
|
||||
}
|
||||
case "socketError": {
|
||||
@@ -153,7 +186,7 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
}
|
||||
case "close": {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, null);
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
class WebSocketTransport implements Transport {
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final ClientConnection clientConnection;
|
||||
private boolean isClosed;
|
||||
private volatile Exception lastError;
|
||||
ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
|
||||
private enum EventType { CLOSE }
|
||||
|
||||
private class ClientConnection extends WebSocketClient {
|
||||
ClientConnection(URI serverUri) {
|
||||
super(serverUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
incoming.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
lastError = ex;
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketTransport(URI uri, Duration timeout) {
|
||||
clientConnection = new ClientConnection(uri);
|
||||
try {
|
||||
if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
|
||||
throw new PlaywrightException("Failed to connect", lastError);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
checkIfClosed();
|
||||
clientConnection.send(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String poll(Duration timeout) {
|
||||
checkIfClosed();
|
||||
try {
|
||||
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to read message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
clientConnection.close();
|
||||
}
|
||||
|
||||
void onClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
void offClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.remove(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
private void checkIfClosed() {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
if (clientConnection.isClosed()) {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,51 +16,76 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.JSHandle;
|
||||
import com.microsoft.playwright.Worker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||
|
||||
class WorkerImpl extends ChannelOwner implements Worker {
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
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(WaitForCloseOptions options, Runnable code) {
|
||||
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.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.add("arg", gson().toJsonTree(serializeArgument(arg)));
|
||||
JsonElement json = sendMessage("evaluateExpressionHandle", params);
|
||||
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,11 +93,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)) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.Page;
|
||||
|
||||
public interface BindingCallback {
|
||||
interface Source {
|
||||
BrowserContext context();
|
||||
Page page();
|
||||
Frame frame();
|
||||
}
|
||||
|
||||
Object call(Source source, Object... args);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user