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

Compare commits

..

67 Commits

Author SHA1 Message Date
Yury Semikhatsky 44f355357a chore(release-1.12): set version to 1.12.0 (#477) 2021-06-09 09:12:46 -07:00
Yury Semikhatsky cc3cc401e5 chore(release-1.12): update driver to 1.12.0-1623185968000 (#476) 2021-06-08 15:06:47 -07:00
Yury Semikhatsky 8c93ddf232 feat: roll driver, deprecated channel enum (#468) 2021-06-04 09:26:52 -07:00
Yury Semikhatsky 502965ffec chore: update deps version in examples project (#459) 2021-05-24 13:12:27 -07:00
Yury Semikhatsky 9a994aba70 test: disable test that flakes in Chromium (#457) 2021-05-21 09:40:19 -07:00
Yury Semikhatsky a67bf05b05 test(screencast): close context with video before returning from test method (#456) 2021-05-20 15:25:23 -07:00
Yury Semikhatsky a5d5c0d960 test: close file reader when done (#454) 2021-05-20 14:58:23 -07:00
Yury Semikhatsky 8e7f958242 test: disable same site attribute test in WebKit Windows (#455) 2021-05-20 14:57:33 -07:00
Yury Semikhatsky c6d94b918f test: port more browserType.connect() tests (#453) 2021-05-20 14:28:13 -07:00
Yury Semikhatsky 061e2a961a chore: roll to 1.12.0-next-1621527598000 (#452) 2021-05-20 09:44:15 -07:00
Yury Semikhatsky 87d9957486 fix: wait for video to finish even if page was closed (#447) 2021-05-18 22:45:17 -07:00
Yury Semikhatsky b3629a704b test: use @TestInstance(PER_CLASS) instead of ThreadLocal (#446) 2021-05-18 12:20:13 -07:00
Yury Semikhatsky 02bd360319 feat: roll driver, implement context tracing and network APIs (#444) 2021-05-17 16:32:44 -07:00
Yury Semikhatsky bcf879b8f0 test: support parallel execution (#443) 2021-05-13 16:47:27 -07:00
Yury Semikhatsky b7ce984969 Revert "fix: manually pipe messages from child process sdtout/stderr (#426)" (#440)
This reverts commit 0fa416b459.
2021-05-12 16:12:16 -07:00
Yury Semikhatsky 47fe3aa076 chore: update working version to 1.12.0-SNAPSHOT (#437) 2021-05-11 11:55:51 -07:00
Yury Semikhatsky 06b88b3d4b fix: support slowMo options in browserType.connect (#434) 2021-05-07 16:21:07 -07:00
Yury Semikhatsky f143ebd053 docs: update browser versions in README.md (#430) 2021-05-07 10:59:34 -07:00
Yury Semikhatsky 6c3c47a9f1 chore: roll driver to 1.11.0-1620331022000 (#429) 2021-05-07 08:06:44 -07:00
Yury Semikhatsky 4049952751 chore: roll driver to 1.11.0-1620262237000 (#428) 2021-05-06 11:37:26 -07:00
Yury Semikhatsky d24eba79dc fix: avoid private field/class access from Gson (#427) 2021-05-06 00:04:52 -07:00
Yury Semikhatsky 0fa416b459 fix: manually pipe messages from child process sdtout/stderr (#426) 2021-05-05 19:24:57 -07:00
Yury Semikhatsky a95f8f3887 feat: support tracing API for chromium (#422) 2021-05-04 09:16:44 -07:00
Yury Semikhatsky 779d50ca53 fix: add back pw:api timstamps (#417) 2021-04-27 11:18:23 -07:00
Yury Semikhatsky 627a940bcd feat: protocol logging timestamps (#415) 2021-04-26 20:32:25 -07:00
Yury Semikhatsky c30e105e36 feat: support extra headers in connect and connectOverCDP (#414) 2021-04-26 14:30:55 -07:00
Yury Semikhatsky 8ee5b8b436 feat: support channel logging (#410) 2021-04-23 14:25:32 -07:00
Yury Semikhatsky 21a7f4da02 feat(inspector): add logging to wait for event methods (#409) 2021-04-21 15:55:17 -07:00
Yury Semikhatsky e995996e4f test: update beforeunload expected text in firefox (#408) 2021-04-21 09:33:17 -07:00
Yury Semikhatsky c23afbf9cf test: check that SameSiteAttribute is properly propagated (#405) 2021-04-21 09:05:16 -07:00
Yury Semikhatsky 2e57a7a101 fix(websocket): use incoming queue with unlimited capacity (#406) 2021-04-21 08:58:55 -07:00
Yury Semikhatsky 20322470b5 chore: roll driver to 1.11.0-next-1618951606000 (#404) 2021-04-20 16:55:37 -07:00
Yury Semikhatsky ed25d1877d chore: roll driver with new connect logic (#400) 2021-04-17 09:16:27 -07:00
Yury Semikhatsky 8e27ec6bc3 test(lolcale): port locale tests from typescript (#395) 2021-04-12 12:30:45 -07:00
Yury Semikhatsky 2d956e4a8d feat: roll driver, support connectOverCDP with plain URL (#392) 2021-04-08 17:07:53 -07:00
Yury Semikhatsky 40c663ccce feat: support connectOverCDP (#387) 2021-04-06 23:20:07 -07:00
Max Schmitt 7f52faf400 devops: adjust pushed Docker tags to be consistent with pw (#386) 2021-04-06 09:36:54 -07:00
Yury Semikhatsky 5cd00cc4c7 chore: roll to 1.11.0-next-1617387566000 (#384) 2021-04-02 14:10:43 -07:00
Yury Semikhatsky 1ff5671b12 feat: roll driver, implement waitForUrl and new Video APIs (#382) 2021-04-02 08:53:26 -07:00
Yury Semikhatsky f332a536b1 devops: use install-deps in docker file (#378) 2021-03-30 13:09:14 -07:00
Yury Semikhatsky 5d5e85502e fix: align test-local-installation version with other projects (#380) 2021-03-30 12:32:53 -07:00
Yury Semikhatsky f8b4e93f25 chore: roll driver to 1.11.0-next-1617087307000 (#379) 2021-03-30 12:29:58 -07:00
Yury Semikhatsky 38aa88d59f chore: roll driver to 1.11.0-next-1617006126000 (#376) 2021-03-29 09:02:39 -07:00
Yury Semikhatsky 91282d3401 docs: point to java specific path on playwright.dev (#373) 2021-03-26 09:41:41 -07:00
Yury Semikhatsky 1904087722 chore: bump working version to 1.11.0-SNAPSHOT (#372) 2021-03-25 18:37:25 -07:00
Yury Semikhatsky db810d1117 devops: remove hardcoded docker version tag (#370) 2021-03-25 18:09:17 -07:00
Yury Semikhatsky 2ffca6a0b4 devops: force set current tag to v1.10.0 to push docker release (#368) 2021-03-25 13:27:21 -07:00
Yury Semikhatsky af1bf963dc devops: add docker release workflow to master branch (#367) 2021-03-25 12:44:20 -07:00
Yury Semikhatsky abada21f3a docs: update system requirements link (#365) 2021-03-25 11:30:23 -07:00
Yury Semikhatsky 34f34d869f docs: update note about API readiness (#364) 2021-03-25 11:27:53 -07:00
Yury Semikhatsky 1e4763c80d docs: add devtools instructions to CONTRIBUTING.md (#361) 2021-03-24 16:27:48 -07:00
Yury Semikhatsky ac7b1e4a7a chore: roll driver v1.10.0 (#360) 2021-03-24 16:14:57 -07:00
Yury Semikhatsky 41735ff8ca devops: fix docker script path (#359) 2021-03-24 14:49:01 -07:00
Yury Semikhatsky 866bf3587c devops: publish docker canary (#358) 2021-03-24 14:32:34 -07:00
Yury Semikhatsky d75a7d76a9 devops(docker): add docker test bot (#357) 2021-03-24 13:56:18 -07:00
Yury Semikhatsky d2b6e1c2b4 feat(docker): download browsers to local cache (#356) 2021-03-23 17:49:02 -07:00
Yury Semikhatsky a334baab49 chore: streamline set_maven_version.sh (#355) 2021-03-23 17:38:26 -07:00
Yury Semikhatsky 31c029a50a fix(docker): set java home, use JDK 14 (#352) 2021-03-22 16:31:52 -07:00
Yury Semikhatsky b1b5b43948 fix(launcher): firefoxUserPrefs serialization (#351) 2021-03-22 14:35:29 -07:00
Yury Semikhatsky bddd52e360 chore: update cli to 1.10.0-next-1616100617000 (#348) 2021-03-18 17:59:05 -07:00
Yury Semikhatsky cbc671dd16 fix: supported driver jar packed into another jar (#342) 2021-03-11 23:58:23 -08:00
Yury Semikhatsky a9a2eba2f6 chore: update alpha revision in the docs (#340) 2021-03-10 12:59:53 -08:00
Yury Semikhatsky 4bcb7afeda tests: port new postData tests from upstream (#335) 2021-03-08 12:29:19 -08:00
Yury Semikhatsky 7ca9b33f46 chore: roll driver to 1.10.0-next-1615230258000 (#334) 2021-03-08 12:20:26 -08:00
Yury Semikhatsky ae15e3050d chore: update working version to 1.10.0-SNAPSHOT (#333) 2021-03-08 11:41:56 -08:00
Yury Semikhatsky ed68a789ef docs: update browser versions, fix wording in readme (#331) 2021-03-06 08:04:03 -08:00
Yury Semikhatsky 93a54d0a52 chore: update version to alpha in docs (#330) 2021-03-05 16:57:59 -08:00
99 changed files with 4658 additions and 870 deletions
@@ -0,0 +1,26 @@
name: "devrelease:docker"
on:
push:
branches:
- master
jobs:
publish-canary-docker:
name: "publish to DockerHub"
runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: tag & publish
run: |
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next-focal
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:sha-${{ github.sha }}
@@ -0,0 +1,36 @@
name: Publish Release Docker
on:
release:
types: [published]
workflow_dispatch:
branches:
- release-*
jobs:
publish-canary-docker:
name: publish to DockerHub
runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: tag & publish
run: |
# GITHUB_REF has a form of `refs/tags/v1.3.0`.
# TAG_NAME would be `v1.3.0`
TAG_NAME=${GITHUB_REF#refs/tags/}
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]];
then
echo "Wrong TAG_NAME format: $TAG_NAME"
exit 1
fi
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:latest
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:focal
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}-focal
+52 -2
View File
@@ -1,4 +1,4 @@
name: Test
name: Build & Test
on:
push:
branches:
@@ -9,7 +9,7 @@ on:
- master
- release-*
jobs:
build:
dev:
timeout-minutes: 30
strategy:
fail-fast: false
@@ -39,3 +39,53 @@ jobs:
run: mvn test --no-transfer-progress
env:
BROWSER: ${{ matrix.browser }}
- name: Test Spring Boot Starter
shell: bash
env:
BROWSER: ${{ matrix.browser }}
run: |
mvn -B install -D skipTests --no-transfer-progress
cd tools/test-spring-boot-starter
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot*.jar
stable:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser-channel: [chrome]
include:
- os: windows-latest
browser-channel: msedge
- os: macos-latest
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
+30
View File
@@ -0,0 +1,30 @@
name: Test Docker
on:
push:
paths:
- '.github/workflows/test_docker.yml'
- 'Dockerfile*'
branches:
- master
- release-*
pull_request:
paths:
- .github/workflows/test_docker.yml
- Dockerfile.*
- scripts/CLI_VERSION
- '**/pom.xml'
branches:
- master
- release-*
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
- name: Test
run: |
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-focal /bin/bash)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
+11 -2
View File
@@ -2,7 +2,16 @@
## How to Contribute
### Getting Code
### Installing Developer Tools
Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubuntu 20.04
just run the following command:
```sh
sudo apt-get install git openjdk-11-jdk maven
```
### Getting the Code
1. Clone this repository
@@ -19,7 +28,7 @@ scripts/download_driver_for_all_platforms.sh
Names of published driver archives can be found at https://github.com/microsoft/playwright-cli/actions
### Compiling and running the tests with Maven
### Building and running the tests with Maven
```bash
mvn compile
+17 -56
View File
@@ -1,71 +1,32 @@
FROM ubuntu:focal
# === INSTALL BROWSER DEPENDENCIES ===
# Install WebKit dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libwoff1 \
libopus0 \
libwebp6 \
libwebpdemux2 \
libenchant1c2a \
libgudev-1.0-0 \
libsecret-1-0 \
libhyphen0 \
libgdk-pixbuf2.0-0 \
libegl1 \
libnotify4 \
libxslt1.1 \
libevent-2.1-7 \
libgles2 \
libxcomposite1 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libepoxy0 \
libgtk-3-0 \
libharfbuzz-icu0
# Install gstreamer and plugins to support video playback in WebKit.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgstreamer-gl1.0-0 \
libgstreamer-plugins-bad1.0-0 \
gstreamer1.0-plugins-good \
gstreamer1.0-libav
# Install Chromium dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 \
libxss1 \
libasound2 \
fonts-noto-color-emoji \
libxtst6
# Install Firefox dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libdbus-glib-1-2 \
libxt6
# Install ffmpeg to bring in audio and video codecs necessary for playing videos in Firefox.
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg
# (Optional) Install XVFB if there's a need to run browsers in headful mode
RUN apt-get update && apt-get install -y --no-install-recommends \
xvfb
# === INSTALL JDK and Maven ===
RUN apt-get update && apt-get install -y --no-install-recommends \
openjdk-8-jdk maven
openjdk-11-jdk maven
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
# Install utilities required for downloading driver
RUN apt-get update && apt-get install -y --no-install-recommends \
curl unzip
# === INSTALL Playwright-java ===
# === INSTALL playwright maven modules & browsers ===
# Browsers will remain downloaded in `/ms-playwright`.
# Note: make sure to set 777 to the registry so that any user can access
# registry.
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN mkdir /ms-playwright && chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
RUN mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && ./scripts/download_driver_for_all_platforms.sh && \
RUN cd /tmp/pw-java && \
./scripts/download_driver_for_all_platforms.sh && \
mvn install -D skipTests --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java
+12 -12
View File
@@ -5,17 +5,17 @@
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.microsoft.playwright/playwright.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack)
#### [Website](https://playwright.dev/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
#### [Website](https://playwright.dev/java/) | [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: |
| Chromium <!-- GEN:chromium-version -->92.0.4498.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->14.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->89.0b6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
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.
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
@@ -35,7 +35,7 @@ Playwright requires **Java 8** or newer.
#### Add Maven dependency
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).
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 one dependency 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:
@@ -43,13 +43,13 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.180.0</version>
<version>1.11.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.
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 multiple Playwright instances each on its own thread.
## Examples
@@ -173,7 +173,9 @@ public class InterceptNetworkRequests {
## Documentation
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.
Check out our [new documentation site](https://playwright.dev/java)!.
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
## Contributing
@@ -181,6 +183,4 @@ Follow [the instructions](https://github.com/microsoft/playwright-java/blob/mast
## 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.
Yes, Playwright for Java is ready. v1.10.0 is the first stable release. Going forward we will adhere to [semantic versioning](https://semver.org/) of the API.
+4 -2
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -44,7 +44,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
@@ -48,6 +48,9 @@ public class DriverJar extends Driver {
p.destroy();
throw new RuntimeException("Timed out waiting for browsers to install");
}
if (p.exitValue() != 0) {
throw new RuntimeException("Failed to install browsers, exit code: " + p.exitValue());
}
}
private static boolean isExecutable(Path filePath) {
@@ -57,7 +60,9 @@ public class DriverJar extends Driver {
private void extractDriverToTempDir() throws URISyntaxException, IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI uri = classloader.getResource("driver/" + platformDir()).toURI();
URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
URI uri = maybeExtractNestedJar(originalUri);
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
Path srcRoot = Paths.get(uri);
@@ -80,12 +85,34 @@ public class DriverJar extends Driver {
}
toPath.toFile().deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri, e);
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
}
});
}
}
private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
if (!"jar".equals(uri.getScheme())) {
return uri;
}
final String JAR_URL_SEPARATOR = "!/";
String[] parts = uri.toString().split("!/");
if (parts.length != 3) {
return uri;
}
String innerJar = String.join(JAR_URL_SEPARATOR, parts[0], parts[1]);
URI jarUri = new URI(innerJar);
try (FileSystem fs = FileSystems.newFileSystem(jarUri, Collections.emptyMap())) {
Path fromPath = Paths.get(jarUri);
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
Files.copy(fromPath, toPath);
toPath.toFile().deleteOnExit();
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
}
}
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
if (name.contains("windows")) {
@@ -31,19 +31,14 @@ public class TestInstall {
void playwrightCliInstalled() throws Exception {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
try {
Path cli = Driver.ensureDriverInstalled();
assertTrue(Files.exists(cli));
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");
} catch (Exception e) {
e.printStackTrace();
assertNull(e);
}
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");
}
}
+4 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
</parent>
<artifactId>driver</artifactId>
@@ -40,6 +40,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
+2 -2
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>0.1-SNAPSHOT</version>
<version>1.12.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -15,7 +15,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.11.1</version>
</dependency>
</dependencies>
<build>
+1 -4
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -50,9 +50,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
@@ -66,7 +66,7 @@ public interface Browser extends AutoCloseable {
public Boolean bypassCSP;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to '{@code light}'.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
/**
@@ -114,9 +114,11 @@ public interface Browser extends AutoCloseable {
*/
public List<String> permissions;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
@@ -124,11 +126,14 @@ public interface Browser extends AutoCloseable {
*/
public Boolean recordHarOmitContent;
/**
* Path on the filesystem to write the HAR file to.
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
/**
* Path to the directory to put videos into.
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public Path recordVideoDir;
/**
@@ -137,6 +142,16 @@ public interface Browser extends AutoCloseable {
* be scaled down if necessary to fit the specified size.
*/
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public ScreenSize screenSize;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -159,7 +174,7 @@ public interface Browser extends AutoCloseable {
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Optional<ViewportSize> viewportSize;
@@ -251,6 +266,17 @@ public interface Browser extends AutoCloseable {
this.recordVideoSize = recordVideoSize;
return this;
}
public NewContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
public NewContextOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
public NewContextOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
public NewContextOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
@@ -286,7 +312,7 @@ public interface Browser extends AutoCloseable {
public Boolean bypassCSP;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to '{@code light}'.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
/**
@@ -334,9 +360,11 @@ public interface Browser extends AutoCloseable {
*/
public List<String> permissions;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
@@ -344,11 +372,14 @@ public interface Browser extends AutoCloseable {
*/
public Boolean recordHarOmitContent;
/**
* Path on the filesystem to write the HAR file to.
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
/**
* Path to the directory to put videos into.
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public Path recordVideoDir;
/**
@@ -357,6 +388,16 @@ public interface Browser extends AutoCloseable {
* be scaled down if necessary to fit the specified size.
*/
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public ScreenSize screenSize;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
@@ -379,7 +420,7 @@ public interface Browser extends AutoCloseable {
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Optional<ViewportSize> viewportSize;
@@ -471,6 +512,17 @@ public interface Browser extends AutoCloseable {
this.recordVideoSize = recordVideoSize;
return this;
}
public NewPageOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
public NewPageOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
public NewPageOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
public NewPageOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
@@ -495,6 +547,33 @@ public interface Browser extends AutoCloseable {
return this;
}
}
class StartTracingOptions {
/**
* specify custom categories to use instead of default.
*/
public List<String> categories;
/**
* A path to write the trace file to.
*/
public Path path;
/**
* captures screenshots in the trace.
*/
public Boolean screenshots;
public StartTracingOptions setCategories(List<String> categories) {
this.categories = categories;
return this;
}
public StartTracingOptions setPath(Path path) {
this.path = path;
return this;
}
public StartTracingOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
}
/**
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
@@ -563,6 +642,59 @@ public interface Browser extends AutoCloseable {
* BrowserContext#newPage BrowserContext.newPage()} to control their exact life times.
*/
Page newPage(NewPageOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
* @param page Optional, if specified, tracing includes screenshots of the given page.
*/
default void startTracing(Page page) {
startTracing(page, null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*/
default void startTracing() {
startTracing(null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
* @param page Optional, if specified, tracing includes screenshots of the given page.
*/
void startTracing(Page page, StartTracingOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> Returns the buffer with trace data.
*/
byte[] stopTracing();
/**
* Returns the browser version.
*/
@@ -37,7 +37,7 @@ import java.util.regex.Pattern;
* // Create a new page inside context.
* Page page = context.newPage();
* page.navigate("https://example.com");
* // Dispose context once it"s no longer needed.
* // Dispose context once it is no longer needed.
* context.close();
* }</pre>
*/
@@ -81,6 +81,55 @@ public interface BrowserContext extends AutoCloseable {
*/
void offPage(Consumer<Page> handler);
/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only
* listen for requests from a particular page, use {@link Page#onRequest Page.onRequest()}.
*
* <p> In order to intercept and mutate requests, see {@link BrowserContext#route BrowserContext.route()} or {@link Page#route
* Page.route()}.
*/
void onRequest(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequest onRequest(handler)}.
*/
void offRequest(Consumer<Request> handler);
/**
* Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use
* {@link Page#onRequestFailed Page.onRequestFailed()}.
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@link BrowserContext#onRequestFinished BrowserContext.onRequestFinished()} event and not with {@link
* BrowserContext#onRequestFailed BrowserContext.onRequestFailed()}.
*/
void onRequestFailed(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequestFailed onRequestFailed(handler)}.
*/
void offRequestFailed(Consumer<Request> handler);
/**
* Emitted when a request finishes successfully after downloading the response body. For a successful response, the
* sequence of events is {@code request}, {@code response} and {@code requestfinished}. To listen for successful requests from a particular
* page, use {@link Page#onRequestFinished Page.onRequestFinished()}.
*/
void onRequestFinished(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequestFinished onRequestFinished(handler)}.
*/
void offRequestFinished(Consumer<Request> handler);
/**
* Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events
* is {@code request}, {@code response} and {@code requestfinished}. To listen for response events from a particular page, use {@link
* Page#onResponse Page.onResponse()}.
*/
void onResponse(Consumer<Response> handler);
/**
* Removes handler that was previously added with {@link #onResponse onResponse(handler)}.
*/
void offResponse(Consumer<Response> handler);
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@@ -363,7 +412,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> See {@link Page#exposeFunction Page.exposeFunction()} for page-only version.
*
* <p> An example of adding an {@code md5} function to all pages in the context:
* <p> An example of adding a {@code sha256} function to all pages in the context:
* <pre>{@code
* import com.microsoft.playwright.*;
*
@@ -377,11 +426,11 @@ public interface BrowserContext extends AutoCloseable {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* context.exposeFunction("sha1", args -> {
* context.exposeFunction("sha256", args -> {
* String text = (String) args[0];
* MessageDigest crypto;
* try {
* crypto = MessageDigest.getInstance("SHA-1");
* crypto = MessageDigest.getInstance("SHA-256");
* } catch (NoSuchAlgorithmException e) {
* return null;
* }
@@ -391,7 +440,7 @@ public interface BrowserContext extends AutoCloseable {
* Page page = context.newPage();
* page.setContent("<script>\n" +
* " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.sha1('PLAYWRIGHT');\n" +
* " document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" +
* " }\n" +
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
@@ -470,7 +519,7 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> An example of a naïve handler that aborts all image requests:
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
@@ -488,9 +537,22 @@ public interface BrowserContext extends AutoCloseable {
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
@@ -501,7 +563,7 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> An example of a naïve handler that aborts all image requests:
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
@@ -519,9 +581,22 @@ public interface BrowserContext extends AutoCloseable {
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
@@ -532,7 +607,7 @@ public interface BrowserContext extends AutoCloseable {
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* <p> An example of a naïve handler that aborts all image requests:
* <p> An example of a naive handler that aborts all image requests:
* <pre>{@code
* BrowserContext context = browser.newContext();
* context.route("**\/*.{png,jpg,jpeg}", route -> route.abort());
@@ -550,9 +625,22 @@ public interface BrowserContext extends AutoCloseable {
* browser.close();
* }</pre>
*
* <p> It is possible to examine the request to decide the route action. For example, mocking all requests that contain some
* post data, and leaving all other requests as is:
* <pre>{@code
* context.route("/api/**", route -> {
* if (route.request().postData().contains("my-string"))
* route.fulfill(new Route.FulfillOptions().setBody("mocked-data"));
* else
* route.resume();
* });
* }</pre>
*
* <p> Page routes (set up with {@link Page#route Page.route()}) take precedence over browser context routes when request
* matches both handlers.
*
* <p> To remove a route with its handler you can use {@link BrowserContext#unroute BrowserContext.unroute()}.
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
@@ -625,6 +713,7 @@ public interface BrowserContext extends AutoCloseable {
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*/
String storageState(StorageStateOptions options);
Tracing tracing();
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
@@ -42,6 +42,10 @@ import java.util.*;
*/
public interface BrowserType {
class ConnectOptions {
/**
* Additional HTTP headers to be sent with web socket connect request. Optional.
*/
public Map<String, String> headers;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@@ -53,6 +57,10 @@ public interface BrowserType {
*/
public Double timeout;
public ConnectOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public ConnectOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
@@ -62,12 +70,47 @@ public interface BrowserType {
return this;
}
}
class ConnectOverCDPOptions {
/**
* Additional HTTP headers to be sent with connect request. Optional.
*/
public Map<String, String> headers;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
*/
public Double timeout;
public ConnectOverCDPOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public ConnectOverCDPOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
}
public ConnectOverCDPOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class LaunchOptions {
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public List<String> args;
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
@@ -139,11 +182,24 @@ public interface BrowserType {
* disable timeout.
*/
public Double timeout;
/**
* If specified, traces are saved into this directory.
*/
public Path tracesDir;
public LaunchOptions setArgs(List<String> args) {
this.args = args;
return this;
}
@Deprecated
public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
public LaunchOptions setChannel(String channel) {
this.channel = channel;
return this;
}
public LaunchOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
@@ -207,6 +263,10 @@ public interface BrowserType {
this.timeout = timeout;
return this;
}
public LaunchOptions setTracesDir(Path tracesDir) {
this.tracesDir = tracesDir;
return this;
}
}
class LaunchPersistentContextOptions {
/**
@@ -223,12 +283,18 @@ public interface BrowserType {
*/
public Boolean bypassCSP;
/**
* Enable Chromium sandboxing. Defaults to {@code true}.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public Boolean chromiumSandbox;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to '{@code light}'.
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
*/
public ColorScheme colorScheme;
/**
@@ -251,8 +317,8 @@ public interface BrowserType {
public Map<String, String> env;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled
* Chromium, Firefox or WebKit, use at your own risk.
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
* or WebKit, use at your own risk.
*/
public Path executablePath;
/**
@@ -333,11 +399,14 @@ public interface BrowserType {
*/
public Boolean recordHarOmitContent;
/**
* Path on the filesystem to write the HAR file to.
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
/**
* Path to the directory to put videos into.
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public Path recordVideoDir;
/**
@@ -346,9 +415,18 @@ public interface BrowserType {
* be scaled down if necessary to fit the specified size.
*/
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "no-preference"}.
*/
public ReducedMotion reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
*/
public ScreenSize screenSize;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public Double slowMo;
/**
@@ -362,12 +440,16 @@ public interface BrowserType {
* metaZones.txt</a> for a list of supported timezone IDs.
*/
public String timezoneId;
/**
* If specified, traces are saved into this directory.
*/
public Path tracesDir;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
*/
public Optional<ViewportSize> viewportSize;
@@ -383,6 +465,15 @@ public interface BrowserType {
this.bypassCSP = bypassCSP;
return this;
}
@Deprecated
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel;
return this;
}
public LaunchPersistentContextOptions setChromiumSandbox(boolean chromiumSandbox) {
this.chromiumSandbox = chromiumSandbox;
return this;
@@ -507,6 +598,17 @@ public interface BrowserType {
this.recordVideoSize = recordVideoSize;
return this;
}
public LaunchPersistentContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = reducedMotion;
return this;
}
public LaunchPersistentContextOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
public LaunchPersistentContextOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
return this;
}
public LaunchPersistentContextOptions setSlowMo(double slowMo) {
this.slowMo = slowMo;
return this;
@@ -519,6 +621,10 @@ public interface BrowserType {
this.timezoneId = timezoneId;
return this;
}
public LaunchPersistentContextOptions setTracesDir(Path tracesDir) {
this.tracesDir = tracesDir;
return this;
}
public LaunchPersistentContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
@@ -545,6 +651,30 @@ public interface BrowserType {
* @param wsEndpoint A browser websocket endpoint to connect to.
*/
Browser connect(String wsEndpoint, ConnectOptions options);
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
*/
default Browser connectOverCDP(String endpointURL) {
return connectOverCDP(endpointURL, null);
}
/**
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
*/
Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options);
/**
* A path where Playwright expects to find a bundled browser executable.
*/
@@ -22,11 +22,17 @@ import java.util.*;
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsole Page.onConsole()} event.
*/
public interface ConsoleMessage {
/**
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsole Page.onConsole()}.
*/
List<JSHandle> args();
/**
* URL of the resource followed by 0-based line and column numbers in the resource formatted as {@code URL:line:column}.
*/
String location();
/**
* The text of the console message.
*/
String text();
/**
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code "dir"}, {@code "dirxml"}, {@code "table"},
@@ -23,13 +23,13 @@ import java.util.*;
/**
* {@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> If {@code downloadsPath} isn't specified, all the downloaded files belonging to the browser context are deleted when the
* browser context is closed. And all downloaded files are deleted when the browser closes.
*
* <p> 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"));
* Download download = page.waitForDownload(() -> page.click("a"));
* // wait for download to complete
* Path path = download.path();
* }</pre>
@@ -52,21 +52,27 @@ public interface Download {
*/
InputStream createReadStream();
/**
* Deletes the downloaded file.
* Deletes the downloaded file. Will wait for the download to finish if necessary.
*/
void delete();
/**
* Returns download error if any.
* Returns download error if any. Will wait for the download to finish if necessary.
*/
String failure();
/**
* Returns path to the downloaded file in case of successful download.
* Get the page that the download belongs to.
*/
Page page();
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
*/
Path path();
/**
* Saves the download to a user-specified path.
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
*
* @param path Path where the download should be saved.
* @param path Path where the download should be copied.
*/
void saveAs(Path path);
/**
@@ -60,12 +60,23 @@ public interface ElementHandle extends JSHandle {
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public CheckOptions setForce(boolean force) {
this.force = force;
@@ -75,10 +86,21 @@ public interface ElementHandle extends JSHandle {
this.noWaitAfter = noWaitAfter;
return this;
}
public CheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
public CheckOptions setPosition(Position position) {
this.position = position;
return this;
}
public CheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public CheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class ClickOptions {
/**
@@ -120,6 +142,12 @@ public interface ElementHandle extends JSHandle {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public ClickOptions setButton(MouseButton button) {
this.button = button;
@@ -156,6 +184,10 @@ public interface ElementHandle extends JSHandle {
this.timeout = timeout;
return this;
}
public ClickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class DblclickOptions {
/**
@@ -193,6 +225,12 @@ public interface ElementHandle extends JSHandle {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public DblclickOptions setButton(MouseButton button) {
this.button = button;
@@ -225,6 +263,10 @@ public interface ElementHandle extends JSHandle {
this.timeout = timeout;
return this;
}
public DblclickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class FillOptions {
/**
@@ -271,6 +313,12 @@ public interface ElementHandle extends JSHandle {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public HoverOptions setForce(boolean force) {
this.force = force;
@@ -291,6 +339,10 @@ public interface ElementHandle extends JSHandle {
this.timeout = timeout;
return this;
}
public HoverOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class PressOptions {
/**
@@ -471,6 +523,12 @@ public interface ElementHandle extends JSHandle {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public TapOptions setForce(boolean force) {
this.force = force;
@@ -495,6 +553,10 @@ public interface ElementHandle extends JSHandle {
this.timeout = timeout;
return this;
}
public TapOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class TypeOptions {
/**
@@ -539,12 +601,23 @@ public interface ElementHandle extends JSHandle {
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public UncheckOptions setForce(boolean force) {
this.force = force;
@@ -554,10 +627,21 @@ public interface ElementHandle extends JSHandle {
this.noWaitAfter = noWaitAfter;
return this;
}
public UncheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
public UncheckOptions setPosition(Position position) {
this.position = position;
return this;
}
public UncheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public UncheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class WaitForElementStateOptions {
/**
@@ -623,20 +707,20 @@ public interface ElementHandle extends JSHandle {
/**
* This method checks the element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method rejects. If the element is already checked, this
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already checked, this
* method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the element, unless
* {@code force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method rejects.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
default void check() {
check(null);
@@ -644,20 +728,20 @@ public interface ElementHandle extends JSHandle {
/**
* This method checks the element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method rejects. If the element is already checked, this
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already checked, this
* method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the element, unless
* {@code force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method rejects.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
void check(CheckOptions options);
/**
@@ -670,10 +754,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
default void click() {
click(null);
@@ -688,10 +772,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
void click(ClickOptions options);
/**
@@ -706,13 +790,13 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to double click in the center of the element, or the specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the first
* click of the {@code dblclick()} triggers a navigation event, this method will reject.</li>
* click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code elementHandle.dblclick()} dispatches two {@code click} events and a single {@code dblclick} event.
*/
@@ -727,20 +811,20 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to double click in the center of the element, or the specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the first
* click of the {@code dblclick()} triggers a navigation event, this method will reject.</li>
* click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code elementHandle.dblclick()} dispatches two {@code click} events and a single {@code dblclick} event.
*/
void dblclick(DblclickOptions options);
/**
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the elment, {@code click}
* is dispatched. This is equivalend to calling <a
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the element,
* {@code click} is dispatched. This is equivalent to calling <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click">element.click()</a>.
* <pre>{@code
* elementHandle.dispatchEvent("click");
@@ -775,8 +859,8 @@ public interface ElementHandle extends JSHandle {
dispatchEvent(type, null);
}
/**
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the elment, {@code click}
* is dispatched. This is equivalend to calling <a
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the element,
* {@code click} is dispatched. This is equivalent to calling <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click">element.click()</a>.
* <pre>{@code
* elementHandle.dispatchEvent("click");
@@ -913,10 +997,15 @@ public interface ElementHandle extends JSHandle {
Object evalOnSelectorAll(String selector, String expression, Object arg);
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, focuses the
* element, fills it and triggers an {@code input} event after filling. If the element is inside the {@code <label>} element that has
* associated <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, that control
* will be filled instead. If the element to be filled is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element,
* this method throws an error. Note that you can pass an empty string to clear the input field.
* element, fills it and triggers an {@code input} event after filling. Note that you can pass an empty string to clear the input
* field.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
*/
@@ -925,10 +1014,15 @@ public interface ElementHandle extends JSHandle {
}
/**
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, focuses the
* element, fills it and triggers an {@code input} event after filling. If the element is inside the {@code <label>} element that has
* associated <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, that control
* will be filled instead. If the element to be filled is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element,
* this method throws an error. Note that you can pass an empty string to clear the input field.
* element, fills it and triggers an {@code input} event after filling. Note that you can pass an empty string to clear the input
* field.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
*/
@@ -953,10 +1047,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
default void hover() {
hover(null);
@@ -971,10 +1065,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
void hover(HoverOptions options);
/**
@@ -1033,7 +1127,7 @@ public interface ElementHandle extends JSHandle {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -1059,7 +1153,7 @@ public interface ElementHandle extends JSHandle {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -1120,12 +1214,17 @@ public interface ElementHandle extends JSHandle {
*/
void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1143,12 +1242,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1164,12 +1268,17 @@ public interface ElementHandle extends JSHandle {
*/
List<String> selectOption(String values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1187,12 +1296,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1208,12 +1322,17 @@ public interface ElementHandle extends JSHandle {
*/
List<String> selectOption(ElementHandle values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1231,12 +1350,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1252,12 +1376,17 @@ public interface ElementHandle extends JSHandle {
*/
List<String> selectOption(String[] values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1275,12 +1404,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1296,12 +1430,17 @@ public interface ElementHandle extends JSHandle {
*/
List<String> selectOption(SelectOption values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1319,12 +1458,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1340,12 +1484,17 @@ public interface ElementHandle extends JSHandle {
*/
List<String> selectOption(ElementHandle[] values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1363,12 +1512,17 @@ public interface ElementHandle extends JSHandle {
return selectOption(values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until
* all specified options are present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If element is not a {@code <select>}
* element, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* handle.selectOption("blue");
@@ -1477,10 +1631,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code elementHandle.tap()} requires that the {@code hasTouch} option of the browser context be set to true.
*/
@@ -1497,10 +1651,10 @@ public interface ElementHandle extends JSHandle {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code elementHandle.tap()} requires that the {@code hasTouch} option of the browser context be set to true.
*/
@@ -1552,20 +1706,20 @@ public interface ElementHandle extends JSHandle {
/**
* This method checks the element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method rejects. If the element is already unchecked,
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already unchecked,
* this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the element, unless
* {@code force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method rejects.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
default void uncheck() {
uncheck(null);
@@ -1573,20 +1727,20 @@ public interface ElementHandle extends JSHandle {
/**
* This method checks the element by performing the following steps:
* <ol>
* <li> Ensure that element is a checkbox or a radio input. If not, this method rejects. If the element is already unchecked,
* <li> Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already unchecked,
* this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the element, unless
* {@code force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method rejects.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method rejects.
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*/
void uncheck(UncheckOptions options);
/**
@@ -137,12 +137,23 @@ public interface Frame {
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public CheckOptions setForce(boolean force) {
this.force = force;
@@ -152,10 +163,21 @@ public interface Frame {
this.noWaitAfter = noWaitAfter;
return this;
}
public CheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
public CheckOptions setPosition(Position position) {
this.position = position;
return this;
}
public CheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public CheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class ClickOptions {
/**
@@ -197,6 +219,12 @@ public interface Frame {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public ClickOptions setButton(MouseButton button) {
this.button = button;
@@ -233,6 +261,10 @@ public interface Frame {
this.timeout = timeout;
return this;
}
public ClickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class DblclickOptions {
/**
@@ -270,6 +302,12 @@ public interface Frame {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public DblclickOptions setButton(MouseButton button) {
this.button = button;
@@ -302,6 +340,10 @@ public interface Frame {
this.timeout = timeout;
return this;
}
public DblclickOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class DispatchEventOptions {
/**
@@ -423,6 +465,12 @@ public interface Frame {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public HoverOptions setForce(boolean force) {
this.force = force;
@@ -443,6 +491,10 @@ public interface Frame {
this.timeout = timeout;
return this;
}
public HoverOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class InnerHTMLOptions {
/**
@@ -680,6 +732,12 @@ public interface Frame {
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public TapOptions setForce(boolean force) {
this.force = force;
@@ -704,6 +762,10 @@ public interface Frame {
this.timeout = timeout;
return this;
}
public TapOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class TextContentOptions {
/**
@@ -761,12 +823,23 @@ public interface Frame {
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability/">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it.
*/
public Boolean trial;
public UncheckOptions setForce(boolean force) {
this.force = force;
@@ -776,10 +849,21 @@ public interface Frame {
this.noWaitAfter = noWaitAfter;
return this;
}
public UncheckOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
public UncheckOptions setPosition(Position position) {
this.position = position;
return this;
}
public UncheckOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public UncheckOptions setTrial(boolean trial) {
this.trial = trial;
return this;
}
}
class WaitForFunctionOptions {
/**
@@ -888,6 +972,33 @@ public interface Frame {
return this;
}
}
class WaitForURLOptions {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
* changed by using the {@link BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()},
* {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}, {@link Page#setDefaultNavigationTimeout
* Page.setDefaultNavigationTimeout()} or {@link Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* When to consider operation succeeded, defaults to {@code load}. Events can be either:
* <ul>
* <li> {@code "domcontentloaded"} - consider operation to be finished when the {@code DOMContentLoaded} event is fired.</li>
* <li> {@code "load"} - consider operation to be finished when the {@code load} event is fired.</li>
* <li> {@code "networkidle"} - consider operation to be finished when there are no network connections for at least {@code 500} ms.</li>
* </ul>
*/
public WaitUntilState waitUntil;
public WaitForURLOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public WaitForURLOptions setWaitUntil(WaitUntilState waitUntil) {
this.waitUntil = waitUntil;
return this;
}
}
/**
* Returns the added tag when the script's onload fires or when the script content was injected into frame.
*
@@ -921,19 +1032,19 @@ public interface Frame {
/**
* This method checks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method rejects. If the element is already
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method throws. If the element is already
* checked, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method rejects.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -944,19 +1055,19 @@ public interface Frame {
/**
* This method checks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method rejects. If the element is already
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method throws. If the element is already
* checked, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method rejects.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -966,7 +1077,7 @@ public interface Frame {
/**
* This method clicks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -974,8 +1085,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -986,7 +1097,7 @@ public interface Frame {
/**
* This method clicks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -994,8 +1105,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -1008,17 +1119,17 @@ public interface Frame {
/**
* This method double clicks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to double click in the center of the element, or the specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the first
* click of the {@code dblclick()} triggers a navigation event, this method will reject.</li>
* click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code frame.dblclick()} dispatches two {@code click} events and a single {@code dblclick} event.
*
@@ -1031,17 +1142,17 @@ public interface Frame {
/**
* This method double clicks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to double click in the center of the element, or the specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the first
* click of the {@code dblclick()} triggers a navigation event, this method will reject.</li>
* click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code frame.dblclick()} dispatches two {@code click} events and a single {@code dblclick} event.
*
@@ -1050,8 +1161,8 @@ public interface Frame {
*/
void dblclick(String selector, DblclickOptions options);
/**
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the elment, {@code click}
* is dispatched. This is equivalend to calling <a
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the element,
* {@code click} is dispatched. This is equivalent to calling <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click">element.click()</a>.
* <pre>{@code
* frame.dispatchEvent("button#submit", "click");
@@ -1089,8 +1200,8 @@ public interface Frame {
dispatchEvent(selector, type, eventInit, null);
}
/**
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the elment, {@code click}
* is dispatched. This is equivalend to calling <a
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the element,
* {@code click} is dispatched. This is equivalent to calling <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click">element.click()</a>.
* <pre>{@code
* frame.dispatchEvent("button#submit", "click");
@@ -1127,8 +1238,8 @@ public interface Frame {
dispatchEvent(selector, type, null);
}
/**
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the elment, {@code click}
* is dispatched. This is equivalend to calling <a
* The snippet below dispatches the {@code click} event on the element. Regardless of the visibility state of the element,
* {@code click} is dispatched. This is equivalent to calling <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click">element.click()</a>.
* <pre>{@code
* frame.dispatchEvent("button#submit", "click");
@@ -1334,7 +1445,7 @@ public interface Frame {
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Frame#evaluate Frame.evaluate()} and {@link Frame#evaluateHandle
* Frame.evaluateHandle()} is that [method: Frame.evaluateHandle{@code ] returns }JSHandle`.
* Frame.evaluateHandle()} is that {@link Frame#evaluateHandle Frame.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function, passed to the {@link Frame#evaluateHandle Frame.evaluateHandle()}, returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
@@ -1367,7 +1478,7 @@ public interface Frame {
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Frame#evaluate Frame.evaluate()} and {@link Frame#evaluateHandle
* Frame.evaluateHandle()} is that [method: Frame.evaluateHandle{@code ] returns }JSHandle`.
* Frame.evaluateHandle()} is that {@link Frame#evaluateHandle Frame.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function, passed to the {@link Frame#evaluateHandle Frame.evaluateHandle()}, returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
@@ -1398,10 +1509,12 @@ public interface Frame {
/**
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, focuses the element, fills it and
* triggers an {@code input} event after filling. If the element is inside the {@code <label>} element that has associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, that control will be
* filled instead. If the element to be filled is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this
* method throws an error. Note that you can pass an empty string to clear the input field.
* triggers an {@code input} event after filling. Note that you can pass an empty string to clear the input field.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
*
@@ -1415,10 +1528,12 @@ public interface Frame {
/**
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, focuses the element, fills it and
* triggers an {@code input} event after filling. If the element is inside the {@code <label>} element that has associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, that control will be
* filled instead. If the element to be filled is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this
* method throws an error. Note that you can pass an empty string to clear the input field.
* triggers an {@code input} event after filling. Note that you can pass an empty string to clear the input field.
*
* <p> If the target element is not an {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element, this method throws an error.
* However, if the element is inside the {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
*
@@ -1534,7 +1649,7 @@ public interface Frame {
/**
* This method hovers over an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -1542,8 +1657,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -1554,7 +1669,7 @@ public interface Frame {
/**
* This method hovers over an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -1562,8 +1677,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -1741,7 +1856,7 @@ public interface Frame {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
@@ -1767,7 +1882,7 @@ public interface Frame {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
@@ -1798,12 +1913,18 @@ public interface Frame {
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1823,12 +1944,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1846,12 +1973,18 @@ public interface Frame {
*/
List<String> selectOption(String selector, String values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1871,12 +2004,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1894,12 +2033,18 @@ public interface Frame {
*/
List<String> selectOption(String selector, ElementHandle values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1919,12 +2064,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1942,12 +2093,18 @@ public interface Frame {
*/
List<String> selectOption(String selector, String[] values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1967,12 +2124,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -1990,12 +2153,18 @@ public interface Frame {
*/
List<String> selectOption(String selector, SelectOption values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -2015,12 +2184,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -2038,12 +2213,18 @@ public interface Frame {
*/
List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options);
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -2063,12 +2244,18 @@ public interface Frame {
return selectOption(selector, values, null);
}
/**
* Returns the array of option values that have been successfully selected.
* This method waits for an element matching {@code selector}, waits for <a
* href="https://playwright.dev/java/docs/actionability/">actionability</a> checks, waits until all specified options are
* present in the {@code <select>} element and selects these options.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected. If there's no {@code <select>} element
* matching {@code selector}, the method throws an error.
* <p> If the target element is not a {@code <select>} element, this method throws an error. However, if the element is inside the
* {@code <label>} element that has an associated <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be used
* instead.
*
* <p> Will wait until all specified options are present in the {@code <select>} element.
* <p> Returns the array of option values that have been successfully selected.
*
* <p> Triggers a {@code change} and {@code input} event once all the provided options have been selected.
* <pre>{@code
* // single selection matching the value
* frame.selectOption("select#colors", "blue");
@@ -2198,7 +2385,7 @@ public interface Frame {
/**
* This method taps an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -2206,8 +2393,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code frame.tap()} requires that the {@code hasTouch} option of the browser context be set to true.
*
@@ -2220,7 +2407,7 @@ public interface Frame {
/**
* This method taps an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
@@ -2228,8 +2415,8 @@ public interface Frame {
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* <p> <strong>NOTE:</strong> {@code frame.tap()} requires that the {@code hasTouch} option of the browser context be set to true.
*
@@ -2266,7 +2453,7 @@ public interface Frame {
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
@@ -2285,7 +2472,7 @@ public interface Frame {
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
@@ -2296,19 +2483,19 @@ public interface Frame {
/**
* This method checks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method rejects. If the element is already
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method throws. If the element is already
* unchecked, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method rejects.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -2319,19 +2506,19 @@ public interface Frame {
/**
* This method checks an element matching {@code selector} by performing the following steps:
* <ol>
* <li> Find an element match matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method rejects. If the element is already
* <li> Find an element matching {@code selector}. If there is none, wait until a matching element is attached to the DOM.</li>
* <li> Ensure that matched element is a checkbox or a radio input. If not, this method throws. If the element is already
* unchecked, this method returns immediately.</li>
* <li> Wait for <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks on the matched element,
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method rejects.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method rejects with a {@code TimeoutError}.
* Passing zero timeout disables this.
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code TimeoutError}. Passing
* zero timeout disables this.
*
* @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See <a
* href="https://playwright.dev/java/docs/selectors/">working with selectors</a> for more details.
@@ -2614,5 +2801,71 @@ public interface Frame {
* @param timeout A timeout to wait for
*/
void waitForTimeout(double timeout);
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
default void waitForURL(String url) {
waitForURL(url, null);
}
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
void waitForURL(String url, WaitForURLOptions options);
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
default void waitForURL(Pattern url) {
waitForURL(url, null);
}
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
void waitForURL(Pattern url, WaitForURLOptions options);
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
default void waitForURL(Predicate<String> url) {
waitForURL(url, null);
}
/**
* Waits for the frame to navigate to the given URL.
* <pre>{@code
* frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
*/
void waitForURL(Predicate<String> url, WaitForURLOptions options);
}
@@ -133,7 +133,7 @@ public interface Keyboard {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
@@ -170,7 +170,7 @@ public interface Keyboard {
* <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> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
@@ -202,6 +202,8 @@ public interface Keyboard {
*
* <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> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
default void type(String text) {
@@ -220,6 +222,8 @@ public interface Keyboard {
*
* <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> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
void type(String text, TypeOptions options);
File diff suppressed because it is too large Load Diff
@@ -41,11 +41,11 @@ import java.util.*;
*/
public interface Playwright extends AutoCloseable {
/**
* This object can be used to launch or connect to Chromium, returning instances of {@code ChromiumBrowser}.
* This object can be used to launch or connect to Chromium, returning instances of {@code Browser}.
*/
BrowserType chromium();
/**
* This object can be used to launch or connect to Firefox, returning instances of {@code FirefoxBrowser}.
* This object can be used to launch or connect to Firefox, returning instances of {@code Browser}.
*/
BrowserType firefox();
/**
@@ -54,7 +54,7 @@ public interface Playwright extends AutoCloseable {
*/
Selectors selectors();
/**
* This object can be used to launch or connect to WebKit, returning instances of {@code WebKitBrowser}.
* This object can be used to launch or connect to WebKit, returning instances of {@code Browser}.
*/
BrowserType webkit();
/**
@@ -16,6 +16,10 @@
package com.microsoft.playwright;
/**
* PlaywrightException is thrown whenever certain operations are terminated abnormally, e.g. browser closes while {@link
* Page#evaluate Page.evaluate()} is running. All Playwright exceptions inherit from this class.
*/
public class PlaywrightException extends RuntimeException {
public PlaywrightException(String message) {
super(message);
@@ -20,7 +20,7 @@ import java.util.*;
/**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
* touchscreen can only be used in browser contexts that have been intialized with {@code hasTouch} set to true.
* touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true.
*/
public interface Touchscreen {
/**
@@ -0,0 +1,118 @@
/*
* 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.nio.file.Path;
import java.util.*;
/**
* API for collecting and saving Playwright traces. Playwright traces can be opened using the Playwright CLI after
* Playwright script runs.
*
* <p> Start with specifying the folder traces will be stored in:
* <pre>{@code
* Browser browser = chromium.launch();
* BrowserContext context = browser.newContext();
* context.tracing.start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto("https://playwright.dev");
* context.tracing.stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
public interface Tracing {
class StartOptions {
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder specified
* in {@link BrowserType#launch BrowserType.launch()}.
*/
public String name;
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public Boolean screenshots;
/**
* Whether to capture DOM snapshot on every action.
*/
public Boolean snapshots;
public StartOptions setName(String name) {
this.name = name;
return this;
}
public StartOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
}
}
class StopOptions {
/**
* Export trace into the file with the given name.
*/
public Path path;
public StopOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
default void start() {
start(null);
}
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
void start(StartOptions options);
/**
* Stop tracing.
*/
default void stop() {
stop(null);
}
/**
* Stop tracing.
*/
void stop(StopOptions options);
}
@@ -20,16 +20,27 @@ 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.
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
* <pre>{@code
* System.out.println(page.video().path());
* }</pre>
*/
public interface Video {
/**
* Deletes the video file. Will wait for the video to finish if necessary.
*/
void delete();
/**
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
* upon closing the browser context.
* upon closing the browser context. This method throws when connected remotely.
*/
Path path();
/**
* Saves the video to a user-specified path. It is safe to call this method while the video is still in progress, or after
* the page has closed. This method waits until the page is closed and the video is fully saved.
*
* @param path Path where the video should be saved.
*/
void saveAs(Path path);
}
@@ -35,7 +35,7 @@ public interface WebSocket {
void offClose(Consumer<WebSocket> handler);
/**
* Fired when the websocket recieves a frame.
* Fired when the websocket receives a frame.
*/
void onFrameReceived(Consumer<WebSocketFrame> handler);
/**
@@ -65,7 +65,7 @@ public interface Worker {
* 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
* 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
@@ -82,7 +82,7 @@ public interface Worker {
* 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
* 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
@@ -0,0 +1,75 @@
/*
* 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.PlaywrightException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.writeToFile;
class ArtifactImpl extends ChannelOwner {
boolean isRemote;
public ArtifactImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
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();
}
public void delete() {
sendMessage("delete");
}
public String failure() {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
}
public Path pathAfterFinished() {
if (isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("pathAfterFinished").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
}
public void saveAs(Path path) {
if (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);
}
}
@@ -20,12 +20,18 @@ 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 com.microsoft.playwright.options.BindingCallback;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.FunctionCallback;
import com.microsoft.playwright.options.Geolocation;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -35,10 +41,10 @@ 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;
private final TracingImpl tracing;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
@@ -51,6 +57,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
enum EventType {
CLOSE,
PAGE,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
RESPONSE,
}
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -60,6 +70,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = new TracingImpl(this);
}
@Override
@@ -82,6 +93,46 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
}
@Override
public void offRequest(Consumer<Request> handler) {
listeners.remove(EventType.REQUEST, handler);
}
@Override
public void onRequestFailed(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFAILED, handler);
}
@Override
public void offRequestFailed(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFAILED, handler);
}
@Override
public void onRequestFinished(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFINISHED, handler);
}
@Override
public void offRequestFinished(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFINISHED, handler);
}
@Override
public void onResponse(Consumer<Response> handler) {
listeners.add(EventType.RESPONSE, handler);
}
@Override
public void offResponse(Consumer<Response> handler) {
listeners.remove(EventType.RESPONSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
@@ -92,6 +143,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public Page waitForPage(WaitForPageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.close", () -> waitForPageImpl(options, code));
}
private Page waitForPageImpl(WaitForPageOptions options, Runnable code) {
if (options == null) {
options = new WaitForPageOptions();
}
@@ -105,7 +160,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? emptyList() : asList(url));
return cookies(url == null ? new ArrayList<>() : asList(url));
}
private void closeImpl() {
@@ -177,7 +232,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = emptyList();
urls = new ArrayList<>();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
@@ -225,7 +280,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new GrantPermissionsOptions();
}
if (permissions == null) {
permissions = emptyList();
permissions = new ArrayList<>();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
@@ -344,6 +399,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
@Override
public Tracing tracing() {
return tracing;
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(url), handler);
@@ -395,14 +455,58 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
listeners.notify(EventType.PAGE, page);
pages.add(page);
listeners.notify(EventType.PAGE, page);
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
if (binding != null) {
bindingCall.call(binding);
}
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUEST, request);
}
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFAILED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFAILED, request);
}
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFINISHED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFINISHED, request);
}
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("close".equals(event)) {
didClose();
}
@@ -27,10 +27,7 @@ import com.microsoft.playwright.PlaywrightException;
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.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -40,7 +37,8 @@ import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
public boolean isRemote;
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
enum EventType {
@@ -67,7 +65,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
private void closeImpl() {
if (isRemote) {
if (isConnectedOverWebSocket) {
try {
connection.close();
} catch (IOException e) {
@@ -182,6 +180,34 @@ class BrowserImpl extends ChannelOwner implements Browser {
return withLogging("Browser.newPage", () -> newPageImpl(options));
}
@Override
public void startTracing(Page page, StartTracingOptions options) {
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
}
private void startTracingImpl(Page page, StartTracingOptions options) {
if (options == null) {
options = new StartTracingOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
JsonObject jsonPage = new JsonObject();
jsonPage.addProperty("guid", ((PageImpl) page).guid);
params.add("page", jsonPage);
}
sendMessage("startTracing", params);
}
@Override
public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
}
private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
PageImpl page = context.newPage();
@@ -27,6 +27,8 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -58,26 +60,38 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
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();
Map<String, String> headers = Collections.emptyMap();
Duration slowMo = null;
if (options != null) {
if (options.timeout != null) {
timeout = Duration.ofMillis(Math.round(options.timeout));
}
};
if (options.headers != null) {
headers = options.headers;
}
if (options.slowMo != null) {
slowMo = Duration.ofMillis(options.slowMo.intValue());
}
}
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), headers, timeout, slowMo);
Connection connection = new Connection(transport);
PlaywrightImpl playwright = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
}
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isRemote = true;
browser.isConnectedOverWebSocket = true;
Consumer<WebSocketTransport> connectionCloseListener = t -> browser.notifyRemoteClosed();
transport.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.sharedSelectors.removeChannel(selectors);
playwright.unregisterSelectors();
transport.offClose(connectionCloseListener);
try {
connection.close();
@@ -91,6 +105,34 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
}
@Override
public Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options) {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
}
return withLogging("BrowserType.connectOverCDP", () -> connectOverCDPImpl(endpointURL, options));
}
private Browser connectOverCDPImpl(String endpointURL, ConnectOverCDPOptions options) {
if (options == null) {
options = new ConnectOverCDPOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("sdkLanguage", "java");
params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
return browser;
}
public String executablePath() {
return initializer.get("executablePath").getAsString();
}
@@ -22,6 +22,7 @@ import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
class ChannelOwner extends LoggingSupport {
final Connection connection;
@@ -67,6 +68,11 @@ class ChannelOwner extends LoggingSupport {
objects.clear();
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
return super.withLogging(apiName, new WaitForEventLogger<>(this, apiName, code));
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
return connection.sendMessageAsync(guid, method, params);
}
@@ -36,6 +36,7 @@ import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
import static com.microsoft.playwright.impl.Serialization.gson;
class Message {
@@ -67,6 +68,11 @@ public class Connection {
private int lastId = 0;
private final Path srcDir;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private static final boolean isLogging;
static {
String debug = System.getenv("DEBUG");
isLogging = (debug != null) && debug.contains("pw:channel");
}
class Root extends ChannelOwner {
Root(Connection connection) {
@@ -153,7 +159,11 @@ public class Connection {
metadata.add("stack", currentStackTrace());
message.add("metadata", metadata);
}
transport.send(gson().toJson(message));
String messageString = gson().toJson(message);
if (isLogging) {
logWithTimestamp("SEND ► " + messageString);
}
transport.send(messageString);
return result;
}
@@ -184,6 +194,9 @@ public class Connection {
if (messageString == null) {
return;
}
if (isLogging) {
logWithTimestamp("◀ RECV " + messageString);
}
Gson gson = gson();
Message message = gson.fromJson(messageString, Message.class);
dispatch(message);
@@ -255,6 +268,9 @@ public class Connection {
case "AndroidDevice":
// result = new AndroidDevice(parent, type, guid, initializer);
break;
case "Artifact":
result = new ArtifactImpl(parent, type, guid, initializer);
break;
case "BindingCall":
result = new BindingCall(parent, type, guid, initializer);
break;
@@ -273,9 +289,6 @@ public class Connection {
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
case "Download":
result = new DownloadImpl(parent, type, guid, initializer);
break;
case "Electron":
// result = new Playwright(parent, type, guid, initializer);
break;
@@ -297,9 +310,6 @@ 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;
@@ -16,27 +16,22 @@
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 com.microsoft.playwright.Page;
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;
class DownloadImpl extends LoggingSupport implements Download {
private final PageImpl page;
private final ArtifactImpl artifact;
private final JsonObject initializer;
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();
DownloadImpl(PageImpl page, ArtifactImpl artifact, JsonObject initializer) {
this.page = page;
this.artifact = artifact;
this.initializer = initializer;
}
@Override
@@ -51,58 +46,31 @@ public class DownloadImpl extends ChannelOwner implements Download {
@Override
public InputStream createReadStream() {
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();
});
return withLogging("Download.createReadStream", () -> artifact.createReadStream());
}
@Override
public void delete() {
withLogging("Download.delete", () -> {
sendMessage("delete");
});
withLogging("Download.delete", () -> artifact.delete());
}
@Override
public String failure() {
return withLogging("Download.failure", () -> {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
});
return withLogging("Download.failure", () -> artifact.failure());
}
@Override
public Page page() {
return page;
}
@Override
public Path path() {
return withLogging("Download.path", () -> {
if (browser != null && browser.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
});
return withLogging("Download.path", () -> artifact.pathAfterFinished());
}
@Override
public void saveAs(Path path) {
withLogging("Download.saveAs", () -> {
if (browser != null && browser.isRemote) {
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
writeToFile(stream.stream(), path);
return;
}
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
});
withLogging("Download.saveAs", () -> artifact.saveAs(path));
}
}
@@ -28,6 +28,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.options.LoadState.*;
import static com.microsoft.playwright.impl.Serialization.*;
@@ -856,10 +858,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
return waitForNavigationImpl(code, options, null);
}
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
if (options == null) {
options = new WaitForNavigationOptions();
}
@@ -868,7 +874,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
List<Waitable<Response>> waitables = new ArrayList<>();
UrlMatcher matcher = UrlMatcher.forOneOf(options.url);
if (matcher == null) {
matcher = UrlMatcher.forOneOf(options.url);
}
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
@@ -910,6 +918,37 @@ public class FrameImpl extends ChannelOwner implements Frame {
});
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
@Override
public void waitForURL(Pattern url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
@Override
public void waitForURL(Predicate<String> url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
}
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
if (options == null) {
options = new WaitForURLOptions();
}
if (matcher.test(url())) {
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
convertViaJson(options, WaitForLoadStateOptions.class));
return;
}
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
}
protected void handleEvent(String event, JsonObject params) {
if ("loadstate".equals(event)) {
JsonElement add = params.get("add");
@@ -54,9 +54,14 @@ class LoggingSupport {
}
}
private void logApi(String message) {
static void logWithTimestamp(String message) {
// This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat);
System.err.println(timestamp + " pw:api " + message);
System.err.println(timestamp + " " + message);
}
private void logApi(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:api " + message);
}
}
@@ -44,10 +44,11 @@ public class PageImpl extends ChannelOwner implements Page {
private final KeyboardImpl keyboard;
private final MouseImpl mouse;
private final TouchscreenImpl touchscreen;
final Waitable<?> waitableClosedOrCrashed;
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
@Override
void add(EventType eventType, Consumer<?> listener) {
if (eventType == EventType.FILECHOOSER) {
@@ -70,6 +71,7 @@ public class PageImpl extends ChannelOwner implements Page {
final Set<Worker> workers = new HashSet<>();
private final TimeoutSettings timeoutSettings;
private VideoImpl video;
private final PageImpl opener;
enum EventType {
CLOSE,
@@ -107,6 +109,12 @@ public class PageImpl extends ChannelOwner implements Page {
touchscreen = new TouchscreenImpl(this);
frames.add(mainFrame);
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
waitableClosedOrCrashed = createWaitForCloseHelper();
if (initializer.has("opener")) {
opener = connection.getExistingObject(initializer.getAsJsonObject("opener").get("guid").getAsString());
} else {
opener = null;
}
}
@Override
@@ -119,10 +127,6 @@ public class PageImpl extends ChannelOwner implements Page {
} else {
dialog.dismiss();
}
} else if ("popup".equals(event)) {
String guid = params.getAsJsonObject("page").get("guid").getAsString();
PageImpl popup = connection.getExistingObject(guid);
listeners.notify(EventType.POPUP, popup);
} else if ("worker".equals(event)) {
String guid = params.getAsJsonObject("worker").get("guid").getAsString();
WorkerImpl worker = connection.getExistingObject(guid);
@@ -138,8 +142,10 @@ public class PageImpl extends ChannelOwner implements Page {
ConsoleMessageImpl message = connection.getExistingObject(guid);
listeners.notify(EventType.CONSOLE, message);
} else if ("download".equals(event)) {
String guid = params.getAsJsonObject("download").get("guid").getAsString();
DownloadImpl download = connection.getExistingObject(guid);
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
artifact.isRemote = browserContext.browser() != null && browserContext.browser().isRemote;
DownloadImpl download = new DownloadImpl(this, artifact, params);
listeners.notify(EventType.DOWNLOAD, download);
} else if ("fileChooser".equals(event)) {
String guid = params.getAsJsonObject("element").get("guid").getAsString();
@@ -164,25 +170,6 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.LOAD, this);
} else if ("domcontentloaded".equals(event)) {
listeners.notify(EventType.DOMCONTENTLOADED, this);
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
Request request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
listeners.notify(EventType.REQUESTFAILED, request);
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
Request request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUESTFINISHED, request);
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
} else if ("frameAttached".equals(event)) {
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
FrameImpl frame = connection.getExistingObject(guid);
@@ -211,7 +198,9 @@ public class PageImpl extends ChannelOwner implements Page {
route.resume();
}
} else if ("video".equals(event)) {
video().setRelativePath(params.get("relativePath").getAsString());
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
forceVideo().setArtifact(artifact);
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
@@ -229,6 +218,10 @@ public class PageImpl extends ChannelOwner implements Page {
}
}
void notifyPopup(PageImpl popup) {
listeners.notify(EventType.POPUP, popup);
}
void didClose() {
isClosed = true;
browserContext.pages.remove(this);
@@ -445,6 +438,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Page.waitForClose", () -> waitForCloseImpl(options, code));
}
private Page waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
if (options == null) {
options = new WaitForCloseOptions();
}
@@ -453,22 +450,22 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
return withWaitLogging("Page.waitForConsoleMessage", () -> waitForConsoleMessageImpl(options, code));
}
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
if (options == null) {
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.timeout);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Download waitForDownload(WaitForDownloadOptions options, Runnable code) {
return withWaitLogging("Page.waitForDownload", () -> waitForDownloadImpl(options, code));
}
private Download waitForDownloadImpl(WaitForDownloadOptions options, Runnable code) {
if (options == null) {
options = new WaitForDownloadOptions();
}
@@ -477,6 +474,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public FileChooser waitForFileChooser(WaitForFileChooserOptions options, Runnable code) {
return withWaitLogging("Page.waitForFileChooser", () -> waitForFileChooserImpl(options, code));
}
private FileChooser waitForFileChooserImpl(WaitForFileChooserOptions options, Runnable code) {
// TODO: enable/disable file chooser interception
if (options == null) {
options = new WaitForFileChooserOptions();
@@ -486,6 +487,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Page waitForPopup(WaitForPopupOptions options, Runnable code) {
return withWaitLogging("Page.waitForPopup", () -> waitForPopupImpl(options, code));
}
private Page waitForPopupImpl(WaitForPopupOptions options, Runnable code) {
if (options == null) {
options = new WaitForPopupOptions();
}
@@ -494,6 +499,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public WebSocket waitForWebSocket(WaitForWebSocketOptions options, Runnable code) {
return withWaitLogging("Page.waitForWebSocket", () -> waitForWebSocketImpl(options, code));
}
private WebSocket waitForWebSocketImpl(WaitForWebSocketOptions options, Runnable code) {
if (options == null) {
options = new WaitForWebSocketOptions();
}
@@ -502,12 +511,24 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Worker waitForWorker(WaitForWorkerOptions options, Runnable code) {
return withWaitLogging("Page.waitForWorker", () -> waitForWorkerImpl(options, code));
}
private Worker waitForWorkerImpl(WaitForWorkerOptions options, Runnable code) {
if (options == null) {
options = new WaitForWorkerOptions();
}
return waitForEventWithTimeout(EventType.WORKER, code, options.timeout);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public void close(CloseOptions options) {
if (isClosed) {
@@ -843,14 +864,11 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Page opener() {
return withLogging("Page.opener", () -> {
JsonObject result = sendMessage("opener").getAsJsonObject();
if (!result.has("page")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("page").get("guid").getAsString());
});
public PageImpl opener() {
if (opener == null || opener.isClosed()) {
return null;
}
return opener;
}
@Override
@@ -1144,20 +1162,23 @@ public class PageImpl extends ChannelOwner implements Page {
return mainFrame.url();
}
private VideoImpl forceVideo() {
if (video == null) {
video = new VideoImpl(this);
}
return video;
}
@Override
public VideoImpl video() {
if (video != null) {
return video;
}
// Note: we are creating Video object lazily, because we do not know
// BrowserContextOptions when constructing the page - it is assigned
// too late during launchPersistentContext.
if (browserContext.videosDir == null) {
return null;
}
video = new VideoImpl(this);
// In case of persistent profile, we already have it.
if (initializer.has("videoRelativePath")) {
video.setRelativePath(initializer.get("videoRelativePath").getAsString());
}
return video;
return forceVideo();
}
@Override
@@ -1259,7 +1280,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequest(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
return withLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
return withWaitLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
}
private static Predicate<Request> toRequestPredicate(UrlMatcher matcher) {
@@ -1278,6 +1299,24 @@ public class PageImpl extends ChannelOwner implements Page {
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Request waitForRequestFinished(WaitForRequestFinishedOptions options, Runnable code) {
return withWaitLogging("Page.waitForRequestFinished", () -> waitForRequestFinishedImpl(options, code));
}
private Request waitForRequestFinishedImpl(WaitForRequestFinishedOptions options, Runnable code) {
if (options == null) {
options = new WaitForRequestFinishedOptions();
}
List<Waitable<Request>> waitables = new ArrayList<>();
Predicate<Request> predicate = options.predicate;
waitables.add(new WaitableEvent<>(listeners, EventType.REQUESTFINISHED,
request -> predicate == null || predicate.test(request)));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(toResponsePredicate(new UrlMatcher(urlGlob)), options, code);
@@ -1320,6 +1359,25 @@ public class PageImpl extends ChannelOwner implements Page {
withLogging("Page.waitForTimeout", () -> mainFrame.waitForTimeoutImpl(timeout));
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
@Override
public void waitForURL(Pattern url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
@Override
public void waitForURL(Predicate<String> url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
}
@Override
public List<Worker> workers() {
return new ArrayList<>(workers);
@@ -59,7 +59,16 @@ public class PipeTransport implements Transport {
throw new PlaywrightException("Playwright connection closed");
}
try {
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
if (message == null && readerThread.exception != null) {
try {
close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
throw new PlaywrightException("Failed to read message from driver, pipe closed.", readerThread.exception);
}
return message;
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to read message", e);
}
@@ -84,6 +93,7 @@ class ReaderThread extends Thread {
private final DataInputStream in;
private final BlockingQueue<String> queue;
volatile boolean isClosing;
volatile Exception exception;
private static int readIntLE(DataInputStream in) throws IOException {
int ch1 = in.read();
@@ -109,7 +119,7 @@ class ReaderThread extends Thread {
queue.put(readMessage());
} catch (IOException e) {
if (!isInterrupted() && !isClosing) {
e.printStackTrace();
exception = e;
}
break;
} catch (InterruptedException e) {
@@ -38,6 +38,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
result.driverProcess = p;
result.initSharedSelectors(null);
return result;
} catch (IOException e) {
throw new PlaywrightException("Failed to launch driver", e);
@@ -47,15 +48,30 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
final SharedSelectors sharedSelectors = new SharedSelectors();;
private final SelectorsImpl selectors;
private SharedSelectors 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());
SelectorsImpl channel = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
sharedSelectors.addChannel(channel);
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
}
void initSharedSelectors(PlaywrightImpl parent) {
assert sharedSelectors == null;
if (parent == null) {
sharedSelectors = new SharedSelectors();;
} else {
sharedSelectors = parent.sharedSelectors;
}
sharedSelectors.addChannel(selectors);
}
void unregisterSelectors() {
sharedSelectors.removeChannel(selectors);
}
@Override
@@ -20,8 +20,9 @@ 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.ElementHandle;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -38,6 +39,7 @@ class Serialization {
if (gson == null) {
gson = new GsonBuilder()
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new BrowserChannelSerializer())
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
.registerTypeAdapter(Media.class, new MediaSerializer())
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
@@ -48,8 +50,9 @@ class Serialization {
.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();
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
}
return gson;
}
@@ -274,6 +277,16 @@ class Serialization {
}
}
private static class FirefoxUserPrefsSerializer implements JsonSerializer<Map<String, Object>> {
@Override
public JsonElement serialize(Map<String, Object> src, Type typeOfSrc, JsonSerializationContext context) {
if (!"java.util.Map<java.lang.String, java.lang.Object>".equals(typeOfSrc.getTypeName())) {
throw new PlaywrightException("Unexpected map type: " + typeOfSrc);
}
return context.serialize(src, Map.class);
}
}
private static class StringMapSerializer implements JsonSerializer<Map<String, String>> {
@Override
public JsonElement serialize(Map<String, String> src, Type typeOfSrc, JsonSerializationContext context) {
@@ -284,6 +297,13 @@ class Serialization {
}
}
private static class BrowserChannelSerializer implements JsonSerializer<BrowserChannel> {
@Override
public JsonElement serialize(BrowserChannel src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-'));
}
}
private static class MediaSerializer implements JsonSerializer<Media> {
@Override
public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) {
@@ -0,0 +1,66 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Tracing;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl implements Tracing {
private final BrowserContextImpl context;
TracingImpl(BrowserContextImpl context) {
this.context = context;
}
private void export(Path path) {
JsonObject json = context.sendMessage("tracingExport").getAsJsonObject();
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
if (context.browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(path);
artifact.delete();
}
@Override
public void start(StartOptions options) {
context.withLogging("Tracing.start", () -> startImpl(options));
}
private void startImpl(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
context.sendMessage("tracingStart", params);
}
@Override
public void stop(StopOptions options) {
context.withLogging("Tracing.stop", () -> {
context.sendMessage("tracingStop");
if (options != null && options.path != null) {
export(options.path);
}
});
}
}
@@ -16,32 +16,40 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.microsoft.playwright.FileChooser;
import com.google.gson.*;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
class Utils {
// TODO: generate converter.
static <F, T> T convertViaJson(F f, Class<T> t) {
Gson gson = new Gson();
Gson gson = new GsonBuilder()
// Necessary to avoid access to private fields/classes,
// see https://github.com/microsoft/playwright-java/issues/423
.registerTypeAdapter(Optional.class, new OptionalSerializer())
.create();
String json = gson.toJson(f);
return gson.fromJson(json, t);
}
static boolean isFunctionBody(String expression) {
expression = expression.trim();
return expression.startsWith("function") ||
expression.startsWith("async ") ||
expression.contains("=>");
private static class OptionalSerializer implements JsonSerializer<Optional> {
@Override
public JsonElement serialize(Optional src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
if (src.isPresent()) {
result.add("value", context.serialize(src.get()));
}
return result;
}
}
static Set<Character> escapeGlobChars = new HashSet<>(Arrays.asList('/', '$', '^', '+', '.', '(', ')', '=', '!', '|'));
@@ -171,4 +179,12 @@ class Utils {
return error.endsWith("Browser has been closed") || error.endsWith("Target page, context or browser has been closed");
}
static String createGuid() {
StringBuffer result = new StringBuffer();
Random random = new Random();
for (int i = 0; i < 4; i++) {
result.append(Integer.toHexString(random.nextInt()));
}
return result.toString();
}
}
@@ -16,27 +16,67 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Video;
import java.nio.file.Path;
import java.nio.file.Paths;
class VideoImpl implements Video {
import static java.util.Arrays.asList;
class VideoImpl extends LoggingSupport implements Video {
private final PageImpl page;
private Path fullPath;
private final WaitableResult<ArtifactImpl> waitableArtifact = new WaitableResult<>();
private final boolean isRemote;
VideoImpl(PageImpl page) {
this.page = page;
BrowserImpl browser = page.context().browser();
isRemote = browser != null && browser.isRemote;
}
void setRelativePath(String path) {
fullPath = page.context().videosDir.resolve(path);
void setArtifact(ArtifactImpl artifact) {
artifact.isRemote = isRemote;
waitableArtifact.complete(artifact);
}
private ArtifactImpl waitForArtifact() {
Waitable<ArtifactImpl> waitable = new WaitableRace<>(asList(waitableArtifact, (Waitable<ArtifactImpl>) page.waitableClosedOrCrashed));
return page.runUntil(() -> {}, waitable);
}
@Override
public void delete() {
withLogging("Video.delete", () -> {
try {
waitForArtifact().delete();
} catch (PlaywrightException e) {
}
});
}
@Override
public Path path() {
while (fullPath == null) {
page.connection.processOneMessage();
}
return fullPath;
return withLogging("Video.path", () -> {
if (isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
}
try {
return Paths.get(waitForArtifact().initializer.get("absolutePath").getAsString());
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
});
}
@Override
public void saveAs(Path path) {
withLogging("Video.saveAs", () -> {
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
});
}
}
@@ -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 com.google.gson.JsonObject;
import java.util.function.Supplier;
import static com.microsoft.playwright.impl.Utils.createGuid;
public class WaitForEventLogger<T> implements Supplier<T> {
private final Supplier<T> supplier;
private final ChannelOwner channel;
private final String waitId;
private final String apiName;
WaitForEventLogger(ChannelOwner channelOwner, String apiName, Supplier<T> supplier) {
this.supplier = supplier;
this.channel = channelOwner;
this.apiName = apiName;
this.waitId = createGuid();
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
}
@Override
public T get() {
JsonObject info = new JsonObject();
info.addProperty("phase", "after");
try {
return supplier.get();
} catch (RuntimeException e) {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
}
}
private void sendWaitForEventInfo(JsonObject info) {
info.addProperty("apiName", apiName);
info.addProperty("waitId", waitId);
JsonObject params = new JsonObject();
params.add("info", info);
channel.sendMessageAsync("waitForEventInfo", params);
}
}
@@ -86,6 +86,10 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
@Override
public WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable code) {
return withWaitLogging("WebSocket.waitForFrameReceived", () -> waitForFrameReceivedImpl(options, code));
}
private WebSocketFrame waitForFrameReceivedImpl(WaitForFrameReceivedOptions options, Runnable code) {
if (options == null) {
options = new WaitForFrameReceivedOptions();
}
@@ -94,6 +98,10 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
@Override
public WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable code) {
return withWaitLogging("WebSocket.waitForFrameSent", () -> waitForFrameSentImpl(options, code));
}
private WebSocketFrame waitForFrameSentImpl(WaitForFrameSentOptions options, Runnable code) {
if (options == null) {
options = new WaitForFrameSentOptions();
}
@@ -23,15 +23,17 @@ 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.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
class WebSocketTransport implements Transport {
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
private final BlockingQueue<String> incoming = new LinkedBlockingQueue<>();
private final ClientConnection clientConnection;
private final Duration slowMo;
private boolean isClosed;
private volatile Exception lastError;
ListenerCollection<EventType> listeners = new ListenerCollection<>();
@@ -62,8 +64,11 @@ class WebSocketTransport implements Transport {
}
}
WebSocketTransport(URI uri, Duration timeout) {
WebSocketTransport(URI uri, Map<String, String> headers, Duration timeout, Duration slowMo) {
clientConnection = new ClientConnection(uri);
for (Map.Entry<String, String> entry : headers.entrySet()) {
clientConnection.addHeader(entry.getKey(), entry.getValue());
}
try {
if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
throw new PlaywrightException("Failed to connect", lastError);
@@ -71,6 +76,7 @@ class WebSocketTransport implements Transport {
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to connect", e);
}
this.slowMo = slowMo;
}
@Override
@@ -83,7 +89,11 @@ class WebSocketTransport implements Transport {
public String poll(Duration timeout) {
checkIfClosed();
try {
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
if (slowMo != null && message != null) {
Thread.sleep(slowMo.toMillis());
}
return message;
} catch (InterruptedException e) {
throw new PlaywrightException("Failed to read message", e);
}
@@ -59,6 +59,10 @@ class WorkerImpl extends ChannelOwner implements Worker {
@Override
public Worker waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Worker.waitForClose", () -> waitForCloseImpl(options, code));
}
private Worker waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
if (options == null) {
options = new WaitForCloseOptions();
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
@Deprecated
public enum BrowserChannel {
CHROME,
CHROME_BETA,
CHROME_DEV,
CHROME_CANARY,
MSEDGE,
MSEDGE_BETA,
MSEDGE_DEV,
MSEDGE_CANARY,
@Deprecated FIREFOX_STABLE
}
@@ -18,11 +18,11 @@ package com.microsoft.playwright.options;
public class FilePayload {
/**
* [File] name
* File name
*/
public String name;
/**
* [File] type
* File type
*/
public String mimeType;
/**
@@ -0,0 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ReducedMotion {
REDUCE,
NO_PREFERENCE
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class ScreenSize {
/**
* page width in pixels.
*/
public int width;
/**
* page height in pixels.
*/
public int height;
public ScreenSize(int width, int height) {
this.width = width;
this.height = height;
}
}
@@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
*/
public class TestAutoClose {
@Test
void shouldAllowUsingTryWithResources() throws Exception {
void shouldAllowUsingTryWithResources() {
try (Playwright playwright = Playwright.create();
Browser browser = Utils.getBrowserTypeFromEnv(playwright).launch();
BrowserContext context = browser.newContext();
@@ -16,22 +16,32 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import com.microsoft.playwright.options.BrowserChannel;
import org.junit.jupiter.api.*;
import java.io.IOException;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
import static com.microsoft.playwright.Utils.nextFreePort;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestBase {
static Server server;
static Server httpsServer;
static BrowserType browserType;
static Playwright playwright;
static Browser browser;
static boolean isMac = Utils.getOS() == Utils.OS.MAC;
static boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
static boolean headful;
// Fields reset once before all tests in a class.
Server server;
Server httpsServer;
BrowserType browserType;
Playwright playwright;
Browser browser;
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
static final boolean headful;
static {
String headfulEnv = System.getenv("HEADFUL");
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
}
// Fields reset before each test.
Page page;
BrowserContext context;
@@ -40,43 +50,46 @@ public class TestBase {
}
static boolean isChromium() {
return "chromium".equals(browserType.name());
return "chromium".equals(getBrowserNameFromEnv());
}
static boolean isWebKit() {
return "webkit".equals(browserType.name());
return "webkit".equals(getBrowserNameFromEnv());
}
static boolean isFirefox() {
return "firefox".equals(browserType.name());
return "firefox".equals(getBrowserNameFromEnv());
}
static String getBrowserChannelFromEnv() {
return System.getenv("BROWSER_CHANNEL");
}
static BrowserType.LaunchOptions createLaunchOptions() {
String headfulEnv = System.getenv("HEADFUL");
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
BrowserType.LaunchOptions options;
options = new BrowserType.LaunchOptions();
options.headless = !headful;
options.channel = getBrowserChannelFromEnv();
return options;
}
static void initBrowserType() {
void initBrowserType() {
playwright = Playwright.create();
browserType = Utils.getBrowserTypeFromEnv(playwright);
}
static void launchBrowser(BrowserType.LaunchOptions launchOptions) {
void launchBrowser(BrowserType.LaunchOptions launchOptions) {
initBrowserType();
browser = browserType.launch(launchOptions);
}
@BeforeAll
static void launchBrowser() {
void launchBrowser() {
launchBrowser(createLaunchOptions());
}
@AfterAll
static void closeBrowser() {
void closeBrowser() {
if (browser != null) {
browser.close();
browser = null;
@@ -84,13 +97,13 @@ public class TestBase {
}
@BeforeAll
static void startServer() throws IOException {
server = Server.createHttp(8907);
httpsServer = Server.createHttps(8908);
void startServer() throws IOException {
server = Server.createHttp(nextFreePort());
httpsServer = Server.createHttps(nextFreePort());
}
@AfterAll
static void stopServer() throws IOException {
void stopServer() {
if (server != null) {
server.stop();
server = null;
@@ -102,7 +115,7 @@ public class TestBase {
}
@AfterAll
static void closePlaywright() throws Exception {
void closePlaywright() {
if (playwright != null) {
playwright.close();
playwright = null;
@@ -16,6 +16,8 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.BrowserChannel;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import java.util.regex.Pattern;
@@ -66,4 +68,33 @@ public class TestBrowser extends TestBase {
assertTrue(Pattern.matches("^\\d+\\.\\d+.*", browser.version()));
}
}
private static BrowserChannel getBrowserChannelEnumFromEnv() {
String channel = getBrowserChannelFromEnv();
if (channel == null) {
return null;
}
switch (channel) {
case "chrome": return BrowserChannel.CHROME;
case "chrome-beta": return BrowserChannel.CHROME_BETA;
case "chrome-dev": return BrowserChannel.CHROME_DEV;
case "chrome-canary": return BrowserChannel.CHROME_CANARY;
case "msedge": return BrowserChannel.MSEDGE;
case "msedge-beta": return BrowserChannel.MSEDGE_BETA;
case "msedge-dev": return BrowserChannel.MSEDGE_DEV;
case "msedge-canary": return BrowserChannel.MSEDGE_CANARY;
default: throw new IllegalArgumentException("Unknown BROWSER_CHANNEL " + channel);
}
}
@Test
void shouldSupportDeprecatedChannelEnum() {
BrowserChannel channel = getBrowserChannelEnumFromEnv();
Assumptions.assumeTrue(channel != null);
BrowserType.LaunchOptions options = createLaunchOptions();
options.setChannel(channel);
Browser browser = browserType.launch(options);
assertNotNull(browser);
browser.close();
}
}
@@ -43,8 +43,7 @@ public class TestBrowserContextBasic extends TestBase {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => window.open(url)", server.EMPTY_PAGE));
Page popup = page.waitForPopup(() -> page.evaluate("url => window.open(url)", server.EMPTY_PAGE));
assertEquals(context, popup.context());
context.close();
}
@@ -170,6 +169,9 @@ public class TestBrowserContextBasic extends TestBase {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("<a href='" + server.EMPTY_PAGE + "' target='_blank'>Click me</a>");
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
}
});
Page[] popup = {null};
@@ -20,9 +20,11 @@ import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.SameSiteAttribute;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.assertJsonEquals;
import static com.microsoft.playwright.Utils.getOS;
@@ -183,4 +185,24 @@ public class TestBrowserContextCookies extends TestBase {
" sameSite: 'NONE'\n" +
"}]", cookies);
}
static boolean isWebkitWindows() {
return isWebKit() && isWindows;
}
@Test
@DisabledIf(value="isWebkitWindows", disabledReason="Same site is not implemented in curl")
void shouldAcceptSameSiteAttribute() {
context.addCookies(asList(
new Cookie("one", "uno").setUrl(server.EMPTY_PAGE).setSameSite(SameSiteAttribute.LAX),
new Cookie("two", "dos").setUrl(server.EMPTY_PAGE).setSameSite(SameSiteAttribute.STRICT),
new Cookie("three", "tres").setUrl(server.EMPTY_PAGE).setSameSite(SameSiteAttribute.NONE)));
page.navigate(server.EMPTY_PAGE);
Object documentCookie = page.evaluate("document.cookie.split('; ').sort().join('; ')");
assertEquals("one=uno; three=tres; two=dos", documentCookie);
List<SameSiteAttribute> list = context.cookies().stream().map(c -> c.sameSite).sorted().collect(Collectors.toList());
assertEquals(asList( SameSiteAttribute.STRICT, SameSiteAttribute.LAX, SameSiteAttribute.NONE), list);
}
}
@@ -88,8 +88,10 @@ public class TestBrowserContextExposeFunction extends TestBase {
context.exposeFunction("woof", args -> actualArgs.add(args[0]));
context.addInitScript("window['woof']('context')");
Page page = context.newPage();
page.addInitScript("window['woof']('page')");
page.evaluate("undefined");
assertEquals(asList("context"), actualArgs);
actualArgs.clear();
page.addInitScript("window['woof']('page')");
page.reload();
assertEquals(asList("context", "page"), actualArgs);
}
@@ -0,0 +1,171 @@
/*
* 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.options.LoadState;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestBrowserContextLocale extends TestBase {
@Test
void shouldAffectAcceptLanguageHeader() throws ExecutionException, InterruptedException {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
Future<Server.Request> request = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
assertEquals("fr-CH", request.get().headers.get("accept-language").get(0).substring(0, 5));
context.close();
}
@Test
void shouldAffectNavigatorLanguage() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
assertEquals("fr-CH", page.evaluate("() => navigator.language"));
context.close();
}
@Test
void shouldFormatNumber() {
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("en-US"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertEquals("1,000,000.5", page.evaluate("() => (1000000.50).toLocaleString()"));
context.close();
}
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertEquals("1 000 000,5", page.evaluate("() => (1000000.50).toLocaleString().replace(/\\s/g, ' ')"));
context.close();
}
}
@Test
void shouldFormatDate() {
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setLocale("en-US").setTimezoneId("America/Los_Angeles"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
String formatted = "Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)";
assertEquals(formatted, page.evaluate("new Date(1479579154987).toString()"));
context.close();
}
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setLocale("de-DE").setTimezoneId("Europe/Berlin"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
assertEquals("Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)",
page.evaluate("new Date(1479579154987).toString()"));
context.close();
}
}
@Test
void shouldFormatNumberInPopups() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window.open(url)", server.PREFIX + "/formatted-number.html"));
popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
Object result = popup.evaluate("window['result']");
assertEquals("1 000 000,5", result);
context.close();
}
@Test
void shouldAffectNavigatorLanguageInPopups() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-CH"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window.open(url)", server.PREFIX + "/formatted-number.html"));
popup.waitForLoadState(LoadState.DOMCONTENTLOADED);
Object result = popup.evaluate("window.initialNavigatorLanguage");
assertEquals("fr-CH", result);
context.close();
}
@Test
void shouldWorkForMultiplePagesSharingSameProcess() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("ru-RU"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window.open(url)", server.EMPTY_PAGE));
popup = page.waitForPopup(() -> page.evaluate(
"url => window.open(url)", server.EMPTY_PAGE));
context.close();
}
@Test
void shouldBeIsolatedBetweenContexts() {
BrowserContext context1 = browser.newContext(new Browser.NewContextOptions().setLocale("en-US"));
// By default firefox limits number of child web processes to 8.
for (int i = 0; i < 8; i++)
context1.newPage();
BrowserContext context2 = browser.newContext(new Browser.NewContextOptions().setLocale("ru-RU"));
Page page2 = context2.newPage();
String localeNumber = "(1000000.50).toLocaleString()";
for (Page page : context1.pages()) {
assertEquals("1,000,000.5", page.evaluate(localeNumber));
}
assertEquals("1 000 000,5", page2.evaluate(localeNumber));
context1.close();
context2.close();
}
@Test
void shouldNotChangeDefaultLocaleInAnotherContext() {
Function<BrowserContext, String> getContextLocale = (context) -> {
Page page = context.newPage();
return (String) page.evaluate("(new Intl.NumberFormat()).resolvedOptions().locale");
};
String defaultLocale;
{
BrowserContext context = browser.newContext();
defaultLocale = getContextLocale.apply(context);
context.close();
}
String localeOverride = "ru-RU".equals(defaultLocale) ? "de-DE" : "ru-RU";
{
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale(localeOverride));
assertEquals(localeOverride, getContextLocale.apply(context));
context.close();
}
{
BrowserContext context = browser.newContext();
assertEquals(defaultLocale, getContextLocale.apply(context));
context.close();
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextNetworkEvents extends TestBase {
@Test
void BrowserContextEventsRequest() {
List<String> requests = new ArrayList<>();
context.onRequest(request -> requests.add(request.url()));
page.navigate(server.EMPTY_PAGE);
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
Page page1 = context.waitForPage(() -> page.click("a"));
page1.waitForLoadState();
assertEquals(asList(
server.EMPTY_PAGE,
server.PREFIX + "/one-style.html",
server.PREFIX + "/one-style.css"), requests);
}
@Test
void BrowserContextEventsResponse() {
List<String> responses = new ArrayList<>();
context.onResponse(response -> responses.add(response.url()));
page.navigate(server.EMPTY_PAGE);
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
Page page1 = context.waitForPage(() -> page.click("a"));
page1.waitForLoadState();
assertEquals(asList(
server.EMPTY_PAGE,
server.PREFIX + "/one-style.html",
server.PREFIX + "/one-style.css"), responses);
}
@Test
void BrowserContextEventsRequestFailed() {
server.setRoute("/one-style.css", exchange -> exchange.getResponseBody().close());
List<Request> failedRequests = new ArrayList<>();
context.onRequestFailed(request -> failedRequests.add(request));
page.navigate(server.PREFIX + "/one-style.html");
assertEquals(1, failedRequests.size());
assertTrue(failedRequests.get(0).url().contains("one-style.css"));
assertNull(failedRequests.get(0).response());
assertEquals("stylesheet", failedRequests.get(0).resourceType());
assertNotNull(failedRequests.get(0).frame());
}
@Test
void BrowserContextEventsRequestFinished() {
Request[] requestRef = {null};
context.onRequestFinished(r -> requestRef[0] = r);
Response response = page.navigate(server.EMPTY_PAGE);
Request request = response.request();
assertEquals(server.EMPTY_PAGE, request.url());
assertNotNull(request.response());
assertEquals(request.frame(), page.mainFrame());
assertEquals(server.EMPTY_PAGE, request.frame().url());
assertNull(request.failure());
}
@Test
void shouldFireEventsInProperOrder() {
List<String> events = new ArrayList<>();
context.onRequest(r -> events.add("request"));
context.onResponse(r -> events.add("response"));
context.onRequestFinished(r -> events.add("requestfinished"));
Response response = page.navigate(server.EMPTY_PAGE);
assertNull(response.finished());
assertEquals(asList("request", "response", "requestfinished"), events);
}
}
@@ -20,6 +20,7 @@ import com.microsoft.playwright.options.Proxy;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.OutputStreamWriter;
import java.util.Base64;
@@ -29,24 +30,27 @@ import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextProxy extends TestBase {
@Override
@BeforeAll
// Hide base class method to provide extra option.
static void launchBrowser() {
void launchBrowser() {
BrowserType.LaunchOptions options = createLaunchOptions();
options.setProxy(new Proxy("per-context"));
launchBrowser(options);
}
static boolean isChromiumWindows() {
return isChromium() && isWindows;
}
@Test
void shouldThrowForMissingGlobalProxy() {
Browser browser = browserType.launch(createLaunchOptions());
try {
@EnabledIf(value="isChromiumWindows", disabledReason="Platform-specific")
void shouldThrowForMissingGlobalProxyOnChromiumWindows() {
try (Browser browser = browserType.launch(createLaunchOptions())) {
browser.newContext(new Browser.NewContextOptions().setProxy("localhost:" + server.PORT));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy"));
} finally {
browser.close();
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.nio.file.Files;
import java.nio.file.Paths;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeBasic extends TestBase {
@Test
void browserTypeExecutablePathShouldWork() {
Assumptions.assumeTrue(getBrowserChannelFromEnv() == null);
Assumptions.assumeTrue(createLaunchOptions().executablePath == null, "Skip with custom executable path");
String executablePath = browserType.executablePath();
assertTrue(Files.exists(Paths.get(executablePath)));
}
@Test
void browserTypeNameShouldWork() {
assertEquals(getBrowserNameFromEnv(), browserType.name());
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Non-chromium behavior")
void shouldThrowWhenTryingToConnectWithNotChromium() {
try {
browserType.connectOverCDP("foo");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
}
}
}
@@ -17,20 +17,30 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import com.microsoft.playwright.options.WaitForSelectorState;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeConnect extends TestBase {
private static Process browserServer;
private static String wsEndpoint;
private Process browserServer;
private String wsEndpoint;
private static class BrowserServer {
Process process;
@@ -39,11 +49,16 @@ public class TestBrowserTypeConnect extends TestBase {
void kill() throws InterruptedException {
process.destroy();
int exitCode = process.waitFor();
assertEquals(0, exitCode);
// FIXME: 2 tests are failing this check on windows:
// disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed
// shouldThrowWhenUsedAfterIsConnectedReturnsFalse
if (!isWindows) {
assertEquals(0, exitCode);
}
}
}
private static BrowserServer launchBrowserServer() {
private static BrowserServer launchBrowserServer(BrowserType browserType) {
try {
Path driver = Driver.ensureDriverInstalled();
Path dir = driver.getParent();
@@ -67,20 +82,22 @@ public class TestBrowserTypeConnect extends TestBase {
}
}
@Override
@BeforeAll
// Hide base class method to launch browser server and connect to it.
static void launchBrowser() {
void launchBrowser() {
initBrowserType();
BrowserServer r = launchBrowserServer();
BrowserServer r = launchBrowserServer(browserType);
wsEndpoint = r.wsEndpoint;
browserServer = r.process;
browser = browserType.connect(wsEndpoint);
// Do not actually connect to browser, the tests will do it manually.
}
@Override
@AfterAll
static void closeBrowser() {
TestBase.closeBrowser();
void closeBrowser() {
super.closeBrowser();
if (browserServer != null) {
browserServer.destroyForcibly();
browserServer = null;
@@ -108,6 +125,17 @@ public class TestBrowserTypeConnect extends TestBase {
}
}
@Test
void shouldSupportSlowMo() {
Browser browser = browserType.connect(wsEndpoint,
new BrowserType.ConnectOptions().setSlowMo(1));
BrowserContext browserContext = browser.newContext();
Page page = browserContext.newPage();
assertEquals(121, page.evaluate("11 * 11"));
page.navigate(server.EMPTY_PAGE);
browser.close();
}
@Test
void shouldBeAbleToConnectTwoBrowsersAtTheSameTime() {
Browser browser1 = browserType.connect(wsEndpoint);
@@ -128,10 +156,24 @@ public class TestBrowserTypeConnect extends TestBase {
browser2.close();
}
@Test
void shouldSendExtraHeadersWithConnectRequest() throws Exception {
try (WebSocketServerImpl webSocketServer = WebSocketServerImpl.create()) {
try {
browserType.connect("ws://localhost:" + webSocketServer.getPort() + "/ws",
new BrowserType.ConnectOptions().setHeaders(mapOf("User-Agent", "Playwright", "foo", "bar")));
} catch (Exception e) {
}
assertNotNull(webSocketServer.lastClientHandshake);
assertEquals("Playwright", webSocketServer.lastClientHandshake.getFieldValue("User-Agent"));
assertEquals("bar", webSocketServer.lastClientHandshake.getFieldValue("foo"));
}
}
@Test
void disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed() throws InterruptedException {
// Launch another server to not affect other tests.
BrowserServer remote = launchBrowserServer();
BrowserServer remote = launchBrowserServer(browserType);
Browser browser1 = browserType.connect(remote.wsEndpoint);
Browser browser2 = browserType.connect(remote.wsEndpoint);
@@ -182,7 +224,7 @@ public class TestBrowserTypeConnect extends TestBase {
@Test
void shouldThrowWhenUsedAfterIsConnectedReturnsFalse() throws InterruptedException {
// Launch another server to not affect other tests.
BrowserServer server = launchBrowserServer();
BrowserServer server = launchBrowserServer(browserType);
Browser remote = browserType.connect(server.wsEndpoint);
Page page = remote.newPage();
server.kill();
@@ -194,18 +236,226 @@ public class TestBrowserTypeConnect extends TestBase {
assertFalse(remote.isConnected());
}
@Test
void shouldRejectNavigationWhenBrowserCloses() {
Browser remote = browserType.connect(wsEndpoint);
Page page = remote.newPage();
@Test
void shouldThrowWhenCallingWaitForNavigationAfterDisconnect() throws InterruptedException {
BrowserServer server = launchBrowserServer(browserType);
Browser browser = browserType.connect(server.wsEndpoint);
Page page = browser.newPage();
server.setRoute("/one-style.css", r -> {});
page.onRequest(r -> remote.close());
boolean[] disconnected = {false};
browser.onDisconnected(browser1 -> disconnected[0] = true);
server.kill();
while (!disconnected[0]) {
try {
page.navigate(server.PREFIX + "/one-style.html", new Page.NavigateOptions().setTimeout(60000));
fail("did not throw");
page.waitForTimeout(10);
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Playwright connection closed"));
}
}
assertFalse(browser.isConnected());
try {
page.waitForNavigation(() -> {});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Playwright connection closed"));
}
}
@Test
void shouldRejectNavigationWhenBrowserCloses() {
Browser remote = browserType.connect(wsEndpoint);
Page page = remote.newPage();
server.setRoute("/one-style.css", r -> {});
page.onRequest(r -> remote.close());
try {
page.navigate(server.PREFIX + "/one-style.html", new Page.NavigateOptions().setTimeout(60000));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Playwright connection closed"));
}
}
@Test
void shouldEmitCloseEventsOnPagesAndContexts() throws InterruptedException {
BrowserServer server = launchBrowserServer(browserType);
Browser browser = browserType.connect(server.wsEndpoint);
BrowserContext context = browser.newContext();
Page page = context.newPage();
List<String> events = new ArrayList<>();
page.onClose(p -> events.add("page"));
context.onClose(c -> events.add("context"));
server.kill();
while (!events.contains("context")) {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
}
}
assertEquals(Arrays.asList("page", "context"), events);
}
@Test
void shouldRespectSelectors() {
String mycss = "{\n" +
" query(root, selector) {\n" +
" return root.querySelector(selector);\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" return Array.from(root.querySelectorAll(selector));\n" +
" }\n" +
" }";
// Register one engine before connecting.
playwright.selectors().register("mycss1", mycss);
Browser browser1 = browserType.connect(wsEndpoint);
BrowserContext context1 = browser1.newContext();
// Register another engine after creating context.
playwright.selectors().register("mycss2", mycss);
Page page1 = context1.newPage();
page1.setContent("<div>hello</div>");
assertEquals("hello", page1.innerHTML("css=div"));
assertEquals("hello", page1.innerHTML("mycss1=div"));
assertEquals("hello", page1.innerHTML("mycss2=div"));
Browser browser2 = browserType.connect(wsEndpoint);
// Register third engine after second connect.
playwright.selectors().register("mycss3", mycss);
Page page2 = browser2.newPage();
page2.setContent("<div>hello</div>");
assertEquals("hello", page2.innerHTML("css=div"));
assertEquals("hello", page2.innerHTML("mycss1=div"));
assertEquals("hello", page2.innerHTML("mycss2=div"));
assertEquals("hello", page2.innerHTML("mycss3=div"));
browser1.close();
browser2.close();
}
@Test
void shouldNotThrowOnCloseAfterDisconnect() throws InterruptedException {
BrowserServer remoteServer = launchBrowserServer(browserType);
Browser browser = browserType.connect(remoteServer.wsEndpoint);
Page page = browser.newPage();
remoteServer.kill();
while (browser.isConnected()) {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
}
}
browser.close();
}
@Test
void shouldNotThrowOnContextCloseAfterDisconnect() throws InterruptedException {
BrowserServer remoteServer = launchBrowserServer(browserType);
Browser browser = browserType.connect(remoteServer.wsEndpoint);
BrowserContext context = browser.newContext();
Page page = context.newPage();
remoteServer.kill();
while (browser.isConnected()) {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
}
}
context.close();
}
@Test
void shouldNotThrowOnPageCloseAfterDisconnect() throws InterruptedException {
BrowserServer remoteServer = launchBrowserServer(browserType);
Browser browser = browserType.connect(remoteServer.wsEndpoint);
Page page = browser.newPage();
remoteServer.kill();
while (browser.isConnected()) {
try {
page.waitForTimeout(10);
} catch (PlaywrightException e) {
}
}
page.close();
}
@Test
void shouldSaveAsVideosFromRemoteBrowser(@TempDir Path tempDir) {
Path videosPath = tempDir.resolve("videosPath");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordVideoDir(videosPath).setRecordVideoSize(320, 240));
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");
page.waitForTimeout(1000);
context.close();
Path savedAsPath = tempDir.resolve("my-video.webm");
page.video().saveAs(savedAsPath);
assertTrue(Files.exists(savedAsPath));
try {
page.video().path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use saveAs() to save a local copy."));
}
}
@Test
void shouldSaveDownload(@TempDir Path tempDir) throws IOException {
server.setRoute("/download", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.getResponseHeaders().add("Content-Disposition", "attachment");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Hello world");
}
});
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
Path nestedPath = tempDir.resolve(Paths.get("these", "are", "directories", "download.txt"));
download.saveAs(nestedPath);
assertTrue(Files.exists(nestedPath));
assertEquals("Hello world", new String(Files.readAllBytes(nestedPath), StandardCharsets.UTF_8));
try {
download.path();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy."));
}
page.close();
}
@Test
void shouldErrorWhenSavingDownloadAfterDeletion(@TempDir Path tempDir) {
server.setRoute("/download", exchange -> {
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
exchange.getResponseHeaders().add("Content-Disposition", "attachment");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Hello world");
}
});
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = tempDir.resolve("download.txt");
download.delete();
try {
download.saveAs(userPath);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"));
}
page.close();
}
}
@@ -0,0 +1,125 @@
/*
* 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.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import static com.microsoft.playwright.Utils.mapOf;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-specific API")
public class TestChromium extends TestBase {
@Override
void createContextAndPage() {
// Do not create anything.
}
private static int nextPort = 9339;
private static String wsEndpointFromUrl(String urlString) throws IOException {
URL url = new URL(urlString);
URLConnection request = url.openConnection();
request.connect();
Reader reader = new InputStreamReader((InputStream) request.getContent());
JsonObject json = new Gson().fromJson(reader, JsonObject.class);
return json.get("webSocketDebuggerUrl").getAsString();
}
@Test
void shouldConnectToAnExistingCdpSession() throws IOException {
int port = nextPort++;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
Browser cdpBrowser = browserType.connectOverCDP("http://localhost:" + port);
List<BrowserContext> contexts = cdpBrowser.contexts();
assertEquals(1, contexts.size());
cdpBrowser.close();
}
}
@Test
void shouldConnectToAnExistingCdpSessionTwice() throws IOException {
int port = nextPort++;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
String endpointUrl = "http://localhost:" + port;
Browser cdpBrowser1 = browserType.connectOverCDP(endpointUrl);
Browser cdpBrowser2 = browserType.connectOverCDP(endpointUrl);
List<BrowserContext> contexts1 = cdpBrowser1.contexts();
assertEquals(1, contexts1.size());
Page page1 = contexts1.get(0).newPage();
page1.navigate(server.EMPTY_PAGE);
List<BrowserContext> contexts2 = cdpBrowser2.contexts();
assertEquals(1, contexts2.size());
Page page2 = contexts2.get(0).newPage();
page2.navigate(server.EMPTY_PAGE);
assertEquals(2, contexts1.get(0).pages().size());
assertEquals(2, contexts2.get(0).pages().size());
cdpBrowser1.close();
cdpBrowser2.close();
}
}
@Test
void shouldConnectOverAWsEndpoint() throws IOException {
int port = nextPort++;
try (Browser browserServer = browserType.launch(createLaunchOptions()
.setArgs(asList("--remote-debugging-port=" + port)))) {
String wsEndpoint = wsEndpointFromUrl("http://localhost:" + port + "/json/version/");
Browser cdpBrowser1 = browserType.connectOverCDP(wsEndpoint);
List<BrowserContext> contexts1 = cdpBrowser1.contexts();
assertEquals(1, contexts1.size());
cdpBrowser1.close();
Browser cdpBrowser2 = browserType.connectOverCDP(wsEndpoint);
List<BrowserContext> contexts2 = cdpBrowser2.contexts();
assertEquals(1, contexts2.size());
cdpBrowser2.close();
}
}
@Test
void shouldSendExtraHeadersWithConnectRequest() throws Exception {
try (WebSocketServerImpl webSocketServer = WebSocketServerImpl.create()) {
try {
browserType.connectOverCDP("ws://localhost:" + webSocketServer.getPort() + "/ws",
new BrowserType.ConnectOverCDPOptions().setHeaders(mapOf("User-Agent", "Playwright", "foo", "bar")));
} catch (Exception e) {
}
assertNotNull(webSocketServer.lastClientHandshake);
assertEquals("Playwright", webSocketServer.lastClientHandshake.getFieldValue("User-Agent"));
assertEquals("bar", webSocketServer.lastClientHandshake.getFieldValue("foo"));
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-only API")
public class TestChromiumTracing extends TestBase {
@Test
void shouldOutputATrace(@TempDir Path tempDir) {
try (Page page = browser.newPage()) {
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(outputTraceFile));
page.navigate(server.PREFIX + "/grid.html");
browser.stopTracing();
assertTrue(Files.exists(outputTraceFile));
}
}
@Test
void shouldCreateDirectoriesAsNeeded(@TempDir Path tempDir) {
try (Page page = browser.newPage()) {
Path filePath = tempDir.resolve("these/are/directories/trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(filePath));
page.navigate(server.PREFIX + "/grid.html");
browser.stopTracing();
assertTrue(Files.exists(filePath));
}
}
@Test
void shouldRunWithCustomCategoriesIfProvided(@TempDir Path tempDir) throws IOException {
try (Page page = browser.newPage()) {
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile)
.setCategories(asList("disabled-by-default-v8.cpu_profiler.hires")));
browser.stopTracing();
try (FileReader fileReader = new FileReader(outputTraceFile.toFile())) {
JsonObject traceJson = new Gson().fromJson(fileReader, JsonObject.class);
assertTrue(traceJson.getAsJsonObject("metadata").get("trace-config")
.getAsString().contains("disabled-by-default-v8.cpu_profiler.hires"));
}
}
}
@Test
void shouldThrowIfTracingOnTwoPages(@TempDir Path tempDir) {
try (Page page = browser.newPage()) {
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
Page newPage = browser.newPage();
try {
browser.startTracing(newPage, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
fail("did not throw");
} catch (PlaywrightException e) {
}
newPage.close();
browser.stopTracing();
}
}
@Test
void shouldReturnABuffer(@TempDir Path tempDir) throws IOException {
try (Page page = browser.newPage()) {
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(outputTraceFile));
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
byte[] buf = Files.readAllBytes(outputTraceFile);
assertArrayEquals(buf, trace);
}
}
@Test
void shouldWorkWithoutOptions() {
try (Page page = browser.newPage()) {
browser.startTracing(page);
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
assertNotNull(trace);
}
}
@Test
void shouldSupportABufferWithoutAPath() {
try (Page page = browser.newPage()) {
browser.startTracing(page, new Browser.StartTracingOptions().setScreenshots(true));
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
assertTrue(new String(trace, StandardCharsets.UTF_8).contains("screenshot"));
}
}
}
@@ -213,7 +213,7 @@ public class TestDownload extends TestBase {
download.saveAs(userPath);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Download already deleted. Save before deleting."));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
page.close();
}
@@ -97,8 +97,7 @@ public class TestElementHandleOwnerFrame extends TestBase {
@Test
void shouldWorkForAdoptedElements() {
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
Page popup = page.waitForPopup(() -> page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
JSHandle divHandle = page.evaluateHandle("() => {\n" +
" const div = document.createElement('div');\n" +
" document.body.appendChild(div);\n" +
@@ -0,0 +1,57 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class TestFirefoxLauncher extends TestBase {
@Override
@BeforeAll
// Hide base class method to not launch browser.
void launchBrowser() {
}
@Override
void createContextAndPage() {
// Do nothing
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="skip")
void shouldPassFirefoxUserPreferences() {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().setFirefoxUserPrefs(
mapOf(
"network.proxy.type", 1,
"network.proxy.http", "127.0.0.1",
"network.proxy.http_port", 3333));
launchBrowser(options);
Page page = browser.newPage();
try {
page.navigate("http://example.com");
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
}
}
@@ -23,9 +23,10 @@ import static com.microsoft.playwright.Utils.mapOf;
public class TestLaunch extends TestBase {
@Override
@BeforeAll
// Hide base class method to not launch browser.
static void launchBrowser() {
void launchBrowser() {
}
@Override
@@ -0,0 +1,82 @@
/*
* 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.google.gson.Gson;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestNetworkPostData extends TestBase {
@Test
void shouldReturnCorrectPostDataBufferForUtf8Body() {
page.navigate(server.EMPTY_PAGE);
String value = "baẞ";
Request request = page.waitForRequest("**", () -> {
page.evaluate("({url, value}) => {\n" +
" const request = new Request(url, {\n" +
" method: 'POST',\n" +
" body: JSON.stringify(value),\n" +
" });\n" +
" request.headers.set('content-type', 'application/json;charset=UTF-8');\n" +
" return fetch(request);\n" +
"}", mapOf("url", server.PREFIX + "/title.html", "value", value));
});
assertTrue(Arrays.equals(new Gson().toJson(value).getBytes(StandardCharsets.UTF_8), request.postDataBuffer()));
assertEquals(new Gson().toJson(value), request.postData());
}
@Test
void shouldReturnPostDataWOContentType() {
page.navigate(server.EMPTY_PAGE);
Request request = page.waitForRequest("**", () -> {
page.evaluate("({url}) => {\n" +
" const request = new Request(url, {\n" +
" method: 'POST',\n" +
" body: JSON.stringify({ value: 42 }),\n" +
" });\n" +
" request.headers.set('content-type', '');\n" +
" return fetch(request);\n" +
"}", mapOf("url", server.PREFIX + "/title.html"));
});
assertEquals(new Gson().toJson(mapOf("value", 42)), request.postData());
}
void shouldThrowOnInvalidJSONInPostData() {
// Thre is no postDataJSON() in java
}
@Test
void shouldReturnPostDataForPUTRequests() {
page.navigate(server.EMPTY_PAGE);
Request request = page.waitForRequest("**", () -> {
page.evaluate("({url}) => {\n" +
" const request = new Request(url, {\n" +
" method: 'PUT',\n" +
" body: JSON.stringify({ value: 42 }),\n" +
" });\n" +
" return fetch(request);\n" +
"}", mapOf("url", server.PREFIX + "/title.html"));
});
assertEquals(new Gson().toJson(mapOf("value", 42)), request.postData());
}
}
@@ -28,8 +28,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static com.microsoft.playwright.Utils.attachFrame;
import static com.microsoft.playwright.Utils.mapOf;
import static com.microsoft.playwright.Utils.*;
import static org.junit.jupiter.api.Assertions.*;
public class TestNetworkRequest extends TestBase {
@@ -102,8 +101,13 @@ public class TestNetworkRequest extends TestBase {
assertTrue(response.request().headers().get("user-agent").contains("WebKit"));
}
static boolean isWebKitWindowsOrChromium() {
return (isWebKit() && getOS() == Utils.OS.WINDOWS) || isChromium();
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="fail")
@DisabledIf(value="isWebKitWindowsOrChromium", disabledReason="Flaky, see https://github.com/microsoft/playwright/issues/6690")
void shouldGetTheSameHeadersAsTheServer() throws ExecutionException, InterruptedException {
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
server.setRoute("/empty.html", exchange -> {
@@ -131,7 +135,7 @@ public class TestNetworkRequest extends TestBase {
}
});
Response response = page.waitForResponse("**", () -> {
Object text = page.evaluate("async url => {\n" +
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url);\n" +
" return data.text();\n" +
"}", server.CROSS_PROCESS_PREFIX + "/something");
@@ -67,8 +67,10 @@ public class TestPageBasic extends TestBase {
assertEquals("", dialog.message());
} else if (isWebKit()) {
assertEquals("Leave?", dialog.message());
} else {
} else if (isChromium()) {
assertEquals("This page is asking you to confirm that you want to leave - data you have entered may not be saved.", dialog.message());
} else {
assertEquals("This page is asking you to confirm that you want to leave — information youve entered may not be saved.", dialog.message());
}
dialog.accept();
});
@@ -85,8 +85,7 @@ public class TestPageEmulateMedia extends TestBase {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setColorScheme(DARK));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
Page popup = page.waitForPopup(() -> page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"));
assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches"));
context.close();
@@ -94,8 +93,7 @@ public class TestPageEmulateMedia extends TestBase {
{
Page page = browser.newPage(new Browser.NewPageOptions().setColorScheme(LIGHT));
page.navigate(server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
Page popup = page.waitForPopup(() -> page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"));
assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches"));
page.close();
@@ -81,9 +81,28 @@ public class TestPageEventNetwork extends TestBase {
Request[] requestRef = {null};
page.onRequestFinished(r -> requestRef[0] = r);
Response response = page.navigate(server.EMPTY_PAGE);
assertNull(response.finished());
assertNotNull(response);
assertNull(response.finished());
Request request = requestRef[0];
assertNotNull(request);
assertEquals(response.request(), request);
assertEquals(server.EMPTY_PAGE, request.url());
assertNotNull(request.response());
assertEquals(page.mainFrame(), request.frame());
assertEquals(server.EMPTY_PAGE, request.frame().url());
assertNull(request.failure());
}
@Test
void PageWaitForRequestFinished() {
Response[] responseRef = {null};
Request request = page.waitForRequestFinished(() -> {
responseRef[0] = page.navigate(server.EMPTY_PAGE);
});
assertNotNull(request);
Response response = responseRef[0];
assertNotNull(response);
assertNull(response.finished());
assertEquals(response.request(), request);
assertEquals(server.EMPTY_PAGE, request.url());
assertNotNull(request.response());
@@ -97,9 +116,9 @@ public class TestPageEventNetwork extends TestBase {
List<String> events = new ArrayList<>();
page.onRequest(request -> events.add("request"));
page.onResponse(response -> events.add("response"));
page.onRequestFinished(r -> events.add("requestfinished"));
Response response = page.navigate(server.EMPTY_PAGE);
assertNull(response.finished());
events.add("requestfinished");
assertEquals(asList("request", "response", "requestfinished"), events);
}
@@ -368,6 +368,8 @@ public class TestPageKeyboard extends TestBase {
void shouldSupportMacOSShortcuts() {
// Test for MacOS only
Assumptions.assumeTrue(isMac);
// @see https://github.com/microsoft/playwright/issues/5721
Assumptions.assumeFalse(isFirefox());
page.navigate(server.PREFIX + "/input/textarea.html");
ElementHandle textarea = page.querySelector("textarea");
textarea.type("some text");
@@ -434,8 +434,7 @@ public class TestPageRoute extends TestBase {
Route[] route = {null};
page.route("**/*", r -> route[0] = r);
// Wait for request interception.
page.waitForRequest("**", () ->
page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE));
page.waitForRequest("**", () -> page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE));
// Delete frame to cause request to be canceled.
page.evalOnSelector("iframe", "frame => frame.remove()");
try {
@@ -523,6 +522,7 @@ public class TestPageRoute extends TestBase {
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
void shouldSupportCorsWithPOST() {
page.navigate(server.EMPTY_PAGE);
page.route("**/cars", route -> {
@@ -545,6 +545,7 @@ public class TestPageRoute extends TestBase {
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
void shouldSupportCorsWithCredentials() {
page.navigate(server.EMPTY_PAGE);
page.route("**/cars", route -> {
@@ -597,6 +598,7 @@ public class TestPageRoute extends TestBase {
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="https://github.com/microsoft/playwright/issues/6016")
void shouldSupportCorsForDifferentMethods() {
page.navigate(server.EMPTY_PAGE);
page.route("**/cars", route -> {
@@ -43,7 +43,7 @@ public class TestPageWaitForNavigation extends TestBase {
try {
page.waitForNavigation(
new Page.WaitForNavigationOptions().setUrl("**/frame.html").setTimeout(5000),
() -> page.navigate(server.EMPTY_PAGE));
() -> page.navigate(server.EMPTY_PAGE));
fail("did not throw");
} catch (TimeoutError e) {
assertTrue(e.getMessage().contains("Timeout 5000ms exceeded"));
@@ -152,13 +152,13 @@ public class TestPageWaitForNavigation extends TestBase {
Response response1 = page.waitForNavigation(
new Page.WaitForNavigationOptions().setUrl("**/one-style.html"),
() -> page.navigate(server.PREFIX + "/one-style.html"));
() -> page.navigate(server.PREFIX + "/one-style.html"));
assertNotNull(response1);
assertEquals(server.PREFIX + "/one-style.html", response1.url());
Response response2 = page.waitForNavigation(
new Page.WaitForNavigationOptions().setUrl(Pattern.compile("frame.html$")),
() -> page.navigate(server.PREFIX + "/frame.html"));
() -> page.navigate(server.PREFIX + "/frame.html"));
assertNotNull(response2);
assertEquals(server.PREFIX + "/frame.html", response2.url());
@@ -170,7 +170,7 @@ public class TestPageWaitForNavigation extends TestBase {
throw new RuntimeException(e);
}
}),
() -> page.navigate(server.PREFIX + "/frame.html?foo=bar"));
() -> page.navigate(server.PREFIX + "/frame.html?foo=bar"));
assertNotNull(response3);
assertEquals(server.PREFIX + "/frame.html?foo=bar", response3.url());
}
@@ -208,8 +208,7 @@ public class TestPageWaitForNavigation extends TestBase {
void shouldWorkOnFrame() {
page.navigate(server.PREFIX + "/frames/one-frame.html");
Frame frame = page.frames().get(1);
Response response = frame.waitForNavigation(() ->
frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html"));
Response response = frame.waitForNavigation(() -> frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html"));
assertTrue(response.ok());
assertTrue(response.url().contains("grid.html"));
assertEquals(frame, response.frame());
@@ -0,0 +1,111 @@
/*
* 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.options.WaitUntilState;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageWaitForUrl extends TestBase {
@Test
void shouldWork() {
page.navigate(server.EMPTY_PAGE);
page.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
page.waitForURL("**/grid.html");
}
@Test
void shouldRespectTimeout() {
page.navigate(server.EMPTY_PAGE);
try {
page.waitForURL("**/frame.html", new Page.WaitForURLOptions().setTimeout(2500));
fail("did not throw");
} catch (TimeoutError e) {
assertTrue(e.getMessage().contains("Timeout 2500ms exceeded"));
}
}
@Test
void shouldWorkWithBothDomcontentloadedAndLoad() {
page.navigate(server.PREFIX + "/one-style.html");
page.waitForURL("**/one-style.html", new Page.WaitForURLOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
page.waitForURL("**/one-style.html", new Page.WaitForURLOptions().setWaitUntil(WaitUntilState.LOAD));
}
@Test
void shouldWorkWithClickingOnAnchorLinks() {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a href='#foobar'>foobar</a>");
page.click("a");
page.waitForURL("**/*#foobar");
}
@Test
void shouldWorkWithHistoryPushState() {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a onclick='javascript:pushState()'>SPA</a>\n" +
"<script>\n" +
" function pushState() { history.pushState({}, '', 'wow.html') }\n" +
"</script>");
page.click("a");
page.waitForURL("**/wow.html");
assertEquals(server.PREFIX + "/wow.html", page.url());
}
@Test
void shouldWorkWithHistoryReplaceState() {
page.navigate(server.EMPTY_PAGE);
page.setContent(" <a onclick='javascript:replaceState()'>SPA</a>\n" +
"<script>\n" +
" function replaceState() { history.replaceState({}, '', '/replaced.html') }\n" +
"</script>");
page.click("a");
page.waitForURL("**/replaced.html");
assertEquals(server.PREFIX + "/replaced.html", page.url());
}
@Test
void shouldWorkWithDOMHistoryBackHistoryForward() {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a id=back onclick='javascript:goBack()'>back</a>\n" +
"<a id=forward onclick='javascript:goForward()'>forward</a>\n" +
"<script>\n" +
" function goBack() { history.back(); }\n" +
" function goForward() { history.forward(); }\n" +
" history.pushState({}, '', '/first.html');\n" +
" history.pushState({}, '', '/second.html');\n" +
"</script>");
assertEquals(server.PREFIX + "/second.html", page.url());
page.click("a#back");
page.waitForURL("**/first.html");
assertEquals(server.PREFIX + "/first.html", page.url());
page.click("a#forward");
page.waitForURL("**/second.html");
assertEquals(server.PREFIX + "/second.html", page.url());
}
@Test
void shouldWorkOnFrame() {
page.navigate(server.PREFIX + "/frames/one-frame.html");
Frame frame = page.frames().get(1);
frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
frame.waitForURL("**/grid.html");
}
}
@@ -84,7 +84,7 @@ public class TestRequestContinue extends TestBase {
});
page.navigate(server.EMPTY_PAGE);
assertNotNull(error[0]);
assertTrue(error[0].getMessage().contains("New URL must have same protocol as overriden URL"));
assertTrue(error[0].getMessage().contains("New URL must have same protocol as overridden URL"), error[0].getMessage());
}
@Test
@@ -19,10 +19,13 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestScreencast extends TestBase {
@Test
@@ -36,5 +39,92 @@ public class TestScreencast extends TestBase {
assertTrue(path.startsWith(videosDir));
context.close();
assertTrue(Files.exists(path));
};
}
@Test
void shouldSaveAsVideo(@TempDir Path videosDir) {
BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setRecordVideoDir(videosDir)
.setRecordVideoSize(320, 240)
.setViewportSize(320, 240));
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");
page.waitForTimeout(1000);
context.close();
Path saveAsPath = videosDir.resolve("my-video.webm");
page.video().saveAs(saveAsPath);
assertTrue(Files.exists(saveAsPath));
}
@Test
void saveAsShouldThrowWhenNoVideoFrames(@TempDir Path videosDir) {
try (BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setRecordVideoDir(videosDir)
.setRecordVideoSize(320, 240)
.setViewportSize(320, 240))) {
Page page = context.newPage();
Page popup = context.waitForPage(() -> {
page.evaluate("() => {\n" +
" const win = window.open('about:blank');\n" +
" win.close();\n" +
"}");
});
page.close();
Path saveAsPath = videosDir.resolve("my-video.webm");
try {
popup.video().saveAs(saveAsPath);
} catch (PlaywrightException e) {
// WebKit pauses renderer before win.close() and actually writes something.
if (isWebKit()) {
assertTrue(Files.exists(saveAsPath));
} else {
assertTrue(e.getMessage().contains("Page did not produce any video frames"), e.getMessage());
}
}
}
}
@Test
void shouldDeleteVideo(@TempDir Path videosDir) {
try (BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setRecordVideoDir(videosDir)
.setRecordVideoSize(320, 240)
.setViewportSize(320, 240))) {
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");
page.waitForTimeout(1000);
context.close();
page.video().delete();
Path videoPath = page.video().path();
assertFalse(Files.exists(videoPath));
}
}
@Test
void shouldWaitForVideoFinishWhenPageIsClosed(@TempDir Path videosDir) throws IOException {
try (Browser browser = browserType.launch(createLaunchOptions())) {
BrowserContext context = browser.newContext(
new Browser.NewContextOptions()
.setRecordVideoDir(videosDir)
.setRecordVideoSize(320, 240)
.setViewportSize(320, 240));
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");
page.waitForTimeout(500);
// First close page manually.
page.close();
context.close();
}
List<Path> files = Files.list(videosDir).collect(Collectors.toList());
assertEquals(1, files.size());
assertTrue(Files.exists(files.get(0)));
assertTrue(Files.size(files.get(0)) > 0);
}
}
@@ -0,0 +1,80 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestTracing extends TestBase {
@Override
@BeforeAll
void launchBrowser() {
// The method is replaced by the method below (with temp dir as extra parameter,
// we cannot declare temp dir field as it would be initialized too late).
}
@BeforeAll
void launchBrowser(@TempDir Path tempDir) {
System.out.println("new launchBrowser(");
BrowserType.LaunchOptions options = createLaunchOptions();
options.setTracesDir(tempDir.resolve("trace-dir"));
launchBrowser(options);
}
@Test
void shouldCollectTrace1(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
page.close();
Path traceFile = tempDir.resolve("trace.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
assertTrue(Files.exists(traceFile));
}
@Test
void shouldCollectTwoTraces(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test1")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
context.tracing().start(new Tracing.StartOptions().setName("test2")
.setScreenshots(true).setSnapshots(true));
page.dblclick("'Click'");
page.close();
Path traceFile2 = tempDir.resolve("trace2.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile2));
assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));
}
}
@@ -16,15 +16,11 @@
package com.microsoft.playwright;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@@ -35,35 +31,10 @@ import static org.junit.jupiter.api.Assertions.*;
public class TestWebSocket extends TestBase {
private static WebSocketServerImpl webSocketServer;
private static int WS_SERVER_PORT = 8910;
private static class WebSocketServerImpl extends WebSocketServer {
WebSocketServerImpl(InetSocketAddress address) {
super(address, 1);
}
@Override
public void onOpen(org.java_websocket.WebSocket webSocket, ClientHandshake clientHandshake) {
webSocket.send("incoming");
}
@Override
public void onClose(org.java_websocket.WebSocket webSocket, int i, String s, boolean b) { }
@Override
public void onMessage(org.java_websocket.WebSocket webSocket, String s) { }
@Override
public void onError(WebSocket webSocket, Exception e) { }
@Override
public void onStart() { }
}
@BeforeAll
static void startWebSockerServer() {
webSocketServer = new WebSocketServerImpl(new InetSocketAddress("localhost", WS_SERVER_PORT));
new Thread(webSocketServer).start();
static void startWebSockerServer() throws InterruptedException {
webSocketServer = WebSocketServerImpl.create();
}
@AfterAll
@@ -30,8 +30,7 @@ public class TestWorkers extends TestBase {
@Test
void pageWorkers() {
Worker worker = page.waitForWorker(() ->
page.navigate(server.PREFIX + "/worker/worker.html"));
Worker worker = page.waitForWorker(() -> page.navigate(server.PREFIX + "/worker/worker.html"));
assertTrue(worker.url().contains("worker.js"));
assertEquals("worker function result", worker.evaluate("() => self['workerFunction']()"));
page.navigate(server.EMPTY_PAGE);
@@ -23,14 +23,37 @@ import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
class Utils {
private static final AtomicInteger nextUnusedPort = new AtomicInteger(9000);
private static boolean available(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return true;
} catch (IOException ignored) {
return false;
}
}
static int nextFreePort() {
for (int i = 0; i < 100; i++) {
int port = nextUnusedPort.getAndIncrement();
if (available(port)) {
return port;
}
}
throw new RuntimeException("Cannot find free port: " + nextUnusedPort.get());
}
static void assertJsonEquals(String expected, Object actual) {
JsonElement actualJson = JsonParser.parseString(new Gson().toJson(actual));
assertEquals(JsonParser.parseString(expected), actualJson);
@@ -104,13 +127,16 @@ class Utils {
}
}
static BrowserType getBrowserTypeFromEnv(Playwright playwright) {
static String getBrowserNameFromEnv() {
String browserName = System.getenv("BROWSER");
if (browserName == null) {
browserName = "chromium";
}
return browserName;
}
static BrowserType getBrowserTypeFromEnv(Playwright playwright) {
String browserName = getBrowserNameFromEnv();
switch (browserName) {
case "webkit":
return playwright.webkit();
@@ -0,0 +1,76 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import static com.microsoft.playwright.Utils.nextFreePort;
class WebSocketServerImpl extends WebSocketServer implements AutoCloseable {
volatile ClientHandshake lastClientHandshake;
private final Semaphore startSemaphore = new Semaphore(0);
static WebSocketServerImpl create() throws InterruptedException {
// FIXME: WebSocketServer.stop() doesn't release socket immediately and starting another server
// fails with "Address already in use", so we just allocate new port.
int port = nextFreePort();
WebSocketServerImpl result = new WebSocketServerImpl(new InetSocketAddress("localhost", port));
result.start();
result.startSemaphore.acquire();
return result;
}
private WebSocketServerImpl(InetSocketAddress address) {
super(address, 1);
}
@Override
public void close() throws Exception {
this.stop();
}
@Override
public void onOpen(org.java_websocket.WebSocket webSocket, ClientHandshake clientHandshake) {
lastClientHandshake = clientHandshake;
webSocket.send("incoming");
}
@Override
public void onClose(org.java_websocket.WebSocket webSocket, int i, String s, boolean b) {
}
@Override
public void onMessage(org.java_websocket.WebSocket webSocket, String s) {
}
@Override
public void onError(WebSocket webSocket, Exception e) {
e.printStackTrace();
startSemaphore.release();
}
@Override
public void onStart() {
startSemaphore.release();
}
}
+12 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@@ -113,6 +113,17 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<properties>
<configurationParameters>
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
</configurationParameters>
</properties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
+1 -1
View File
@@ -1 +1 @@
1.9.1-1614983396000
1.12.0-1623185968000
+12 -8
View File
@@ -14,12 +14,16 @@ trap "cd $(pwd -P)" EXIT
cd "$(dirname $0)/.."
VERSION=$1
POM_FILES=(
pom.xml
tools/api-generator/pom.xml
tools/update-docs-version/pom.xml
tools/test-local-installation/pom.xml
tools/test-spring-boot-starter/pom.xml
examples/pom.xml
)
mvn versions:set -DnewVersion=$VERSION
cd tools/api-generator
mvn versions:set -DnewVersion=$VERSION
cd -
cd tools/update-docs-version
mvn versions:set -DnewVersion=$VERSION
for name in ${POM_FILES[*]};
do
mvn versions:set -D newVersion=$VERSION -f $name
done
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
echo "-- tagging: $2"
docker tag $1 $2
docker push $2
+2 -2
View File
@@ -10,7 +10,7 @@ cd "$(dirname $0)/.."
./scripts/generate_api.sh
# Built from source and do local install.
mvn clean install --no-transfer-progress -DskipTests
mvn clean install --no-transfer-progress -D skipTests
echo "Updating browser versions in README.md"
mvn exec:java --f ./tools/update-docs-version -Dexec.mainClass=com.microsoft.playwright.tools.UpdateBrowserVersions
mvn compile exec:java --f ./tools/update-docs-version -D exec.mainClass=com.microsoft.playwright.tools.UpdateBrowserVersions
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -761,6 +761,9 @@ class Field extends Element {
typeStr = "Boolean";
}
}
if (isBrowserChannelOption()) {
typeStr = "Object";
}
output.add(offset + "public " + typeStr + " " + name + ";");
}
@@ -791,6 +794,12 @@ class Field extends Element {
}
}
}
if (isBrowserChannelOption()) {
output.add(offset + "@Deprecated");
writeGenericBuilderMethod(output, offset, parentClass, "BrowserChannel");
}
writeGenericBuilderMethod(output, offset, parentClass, type.toJava());
}
@@ -801,6 +810,10 @@ class Field extends Element {
output.add(offset + " return this;");
output.add(offset + "}");
}
private boolean isBrowserChannelOption() {
return asList("BrowserType.launch.options.channel", "BrowserType.launchPersistentContext.options.channel").contains(jsonPath);
}
}
class Interface extends TypeDefinition {
@@ -867,7 +880,7 @@ class Interface extends TypeDefinition {
if ("Download".equals(jsonName)) {
output.add("import java.io.InputStream;");
}
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors", "Video").contains(jsonName)) {
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors", "Tracing", "Video").contains(jsonName)) {
output.add("import java.nio.file.Path;");
}
output.add("import java.util.*;");
@@ -1016,6 +1029,10 @@ class Enum extends TypeDefinition {
}
enumValues.add(value.substring(1, value.length() - 1).replace("-", "_").toUpperCase());
}
if ("BrowserChannel".equals(jsonName)) {
// Firefox stable 'channel' was removed in 1.12.0
enumValues.add("@Deprecated FIREFOX_STABLE");
}
}
@Override
@@ -1039,7 +1056,7 @@ public class ApiGenerator {
for (JsonElement entry: api) {
String name = entry.getAsJsonObject().get("name").getAsString();
// We write this one manually.
if ("TimeoutError".equals(name)) {
if (asList("PlaywrightException", "TimeoutError").contains(name)) {
continue;
}
List<String> lines = new ArrayList<>();
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
set +x
trap "cd $(pwd -P)" EXIT
cd "$(dirname $0)"
PROJECT_DIR=$(mktemp -d)
echo "Creating project in $PROJECT_DIR"
cp -R . $PROJECT_DIR
cp -R ../../driver-bundle/src/test/ $PROJECT_DIR/src/
cp -R ../../playwright/src/test/ $PROJECT_DIR/src/
cd $PROJECT_DIR
mvn test --no-transfer-progress
rm -rf $PROJECT_DIR
+61
View File
@@ -0,0 +1,61 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.12.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
<compiler.version>1.8</compiler.version>
<gson.version>2.8.6</gson.version>
<junit.version>5.7.0</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<websocket.version>1.5.1</websocket.version>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>${websocket.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${compiler.version}</source>
<target>${compiler.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,2 @@
This derictory is populated with test files from playwright and playwright-driver projects. They are copied
here on each test run.
+36
View File
@@ -0,0 +1,36 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.12.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,46 @@
package com.microsoft.playwright.springboottest;
import com.microsoft.playwright.*;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestApp implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(TestApp.class, args);
}
@Override
public void run(String... args) {
try (Playwright playwright = Playwright.create()) {
BrowserType browserType = getBrowserTypeFromEnv(playwright);
System.out.println("Running test with " + browserType.name());
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
System.out.println(page.evaluate("'SUCCESS: did evaluate in page'"));
}
}
static BrowserType getBrowserTypeFromEnv(Playwright playwright) {
String browserName = System.getenv("BROWSER");
if (browserName == null) {
browserName = "chromium";
}
switch (browserName) {
case "webkit":
return playwright.webkit();
case "firefox":
return playwright.firefox();
case "chromium":
return playwright.chromium();
default:
throw new IllegalArgumentException("Unknown browser: " + browserName);
}
}
}
+9 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>0.190.0-SNAPSHOT</version>
<version>1.12.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
@@ -38,4 +38,12 @@
<version>${project.version}</version>
</dependency>
</dependencies>
<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>