Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23cec0231c | |||
| 1f36d4fbda | |||
| 11d50120b9 | |||
| d709d9e090 | |||
| 5f3331a0b2 | |||
| 104be4728f | |||
| 08776552da | |||
| 70b9e2e034 | |||
| ebf9b09c34 | |||
| cd3b45acd0 | |||
| 528d01b07a | |||
| ab2efd4d80 | |||
| 1eb3f3bb80 | |||
| fcac298050 | |||
| 476e222c93 | |||
| 696610de15 | |||
| 3e7f8017e0 | |||
| 3631357a35 | |||
| 79529a7239 | |||
| 04419c7e5f | |||
| fb508701ef | |||
| 654a4dc12f | |||
| 5eecbb5252 | |||
| 1b58892e58 | |||
| a1ef49cd03 | |||
| 2b0b50358b | |||
| 399de8e899 | |||
| c66b95afb3 | |||
| 1f0f1b06e1 | |||
| 5a0dd8595c | |||
| 518f117fb0 | |||
| 539b18f167 | |||
| 1958a2fa64 | |||
| 87ad579deb | |||
| e83ef2b1c0 | |||
| f23c5d4c01 | |||
| d3f06cefd8 | |||
| 8c93ddf232 | |||
| 502965ffec | |||
| 9a994aba70 | |||
| a67bf05b05 | |||
| a5d5c0d960 | |||
| 8e7f958242 | |||
| c6d94b918f | |||
| 061e2a961a | |||
| 87d9957486 | |||
| b3629a704b | |||
| 02bd360319 | |||
| bcf879b8f0 | |||
| b7ce984969 | |||
| 47fe3aa076 | |||
| 06b88b3d4b | |||
| f143ebd053 | |||
| 6c3c47a9f1 | |||
| 4049952751 | |||
| d24eba79dc | |||
| 0fa416b459 | |||
| a95f8f3887 | |||
| 779d50ca53 | |||
| 627a940bcd | |||
| c30e105e36 | |||
| 8ee5b8b436 | |||
| 21a7f4da02 | |||
| e995996e4f | |||
| c23afbf9cf | |||
| 2e57a7a101 | |||
| 20322470b5 | |||
| ed25d1877d | |||
| 8e27ec6bc3 | |||
| 2d956e4a8d | |||
| 40c663ccce | |||
| 7f52faf400 | |||
| 5cd00cc4c7 | |||
| 1ff5671b12 | |||
| f332a536b1 | |||
| 5d5e85502e | |||
| f8b4e93f25 | |||
| 38aa88d59f | |||
| 91282d3401 | |||
| 1904087722 | |||
| db810d1117 | |||
| 2ffca6a0b4 | |||
| af1bf963dc | |||
| abada21f3a | |||
| 34f34d869f | |||
| 1e4763c80d | |||
| ac7b1e4a7a | |||
| 41735ff8ca | |||
| 866bf3587c | |||
| d75a7d76a9 | |||
| d2b6e1c2b4 | |||
| a334baab49 | |||
| 31c029a50a | |||
| b1b5b43948 | |||
| bddd52e360 | |||
| cbc671dd16 | |||
| a9a2eba2f6 | |||
| 4bcb7afeda | |||
| 7ca9b33f46 | |||
| ae15e3050d | |||
| ed68a789ef | |||
| 93a54d0a52 |
@@ -0,0 +1,75 @@
|
||||
name: Bug Report
|
||||
description: Something doesn't work like it should? Tell us!
|
||||
title: "[Bug]: "
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Playwright version
|
||||
description: Which version of of Playwright are you using?
|
||||
placeholder: ex. 1.12.0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating system
|
||||
multiple: true
|
||||
description: What operating system are you running Playwright on?
|
||||
options:
|
||||
- Windows
|
||||
- MacOS
|
||||
- Linux
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Chromium
|
||||
- Firefox
|
||||
- WebKit
|
||||
- type: textarea
|
||||
id: other-information
|
||||
attributes:
|
||||
label: Other information
|
||||
description: ex. Java version, Linux distribution etc.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened? / Describe the bug
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproducible
|
||||
attributes:
|
||||
label: Code snippet to reproduce your bug
|
||||
description: Help us help you! Put down a short code snippet that illustrates your bug and that we can run and debug locally. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
placeholder: |
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class ExampleReproducible {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output like [Playwright debug logs](https://playwright.dev/docs/debug#verbose-api-logs). This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Join our Slack community
|
||||
url: https://aka.ms/playwright-slack
|
||||
about: Ask questions and discuss with other community members
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Feature request
|
||||
description: Request new features to be added
|
||||
title: "[Feature]: "
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request!
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Feature request
|
||||
description: |
|
||||
Let us know what functionality you'd like to see in Playwright and what is your use case.
|
||||
Do you think others might benefit from this as well?
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,11 @@
|
||||
name: I have a question
|
||||
description: Feel free to ask us your questions!
|
||||
title: "[Question]: "
|
||||
labels: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Your question
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,15 +1,16 @@
|
||||
name: Publish
|
||||
on:
|
||||
workflow_dispatch
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
@@ -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.5.0
|
||||
- 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 }}
|
||||
|
||||
@@ -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
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
@@ -32,10 +33,12 @@ jobs:
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Regenerate APIs
|
||||
run: scripts/generate_api.sh
|
||||
- name: Update browser versions in README
|
||||
run: scripts/update_readme.sh
|
||||
- name: Verify API is up to date
|
||||
run: |
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
echo "ERROR: generated interfaces differ from the current sources:"
|
||||
echo "ERROR: generated interfaces/docs differ from the current sources:"
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+11
-2
@@ -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
@@ -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
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
[](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
|
||||
[](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 -->94.0.4595.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->91.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/#?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.12.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.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.14.1</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>
|
||||
|
||||
@@ -21,25 +21,40 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DriverJar extends Driver {
|
||||
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
|
||||
private final Path driverTempDir;
|
||||
|
||||
DriverJar() throws IOException, URISyntaxException, InterruptedException {
|
||||
driverTempDir = Files.createTempDirectory("playwright-java-");
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
extractDriverToTempDir();
|
||||
installBrowsers();
|
||||
}
|
||||
|
||||
private void installBrowsers() throws IOException, InterruptedException {
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env) throws Exception {
|
||||
extractDriverToTempDir();
|
||||
installBrowsers(env);
|
||||
}
|
||||
|
||||
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
|
||||
String skip = env.get(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
|
||||
if (skip == null) {
|
||||
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
|
||||
}
|
||||
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
|
||||
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
|
||||
return;
|
||||
}
|
||||
String cliFileName = super.cliFileName();
|
||||
Path driver = driverTempDir.resolve(cliFileName);
|
||||
if (!Files.exists(driver)) {
|
||||
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
|
||||
}
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
|
||||
pb.environment().putAll(env);
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -48,6 +63,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 +75,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 +100,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")) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
@@ -31,19 +32,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(Collections.emptyMap());
|
||||
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
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.14.1</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>
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class provides access to playwright-cli. It can be either preinstalled
|
||||
@@ -32,16 +33,23 @@ public abstract class Driver {
|
||||
PreinstalledDriver(Path driverDir) {
|
||||
this.driverDir = driverDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
Path driverDir() {
|
||||
return driverDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Path ensureDriverInstalled() {
|
||||
public static synchronized Path ensureDriverInstalled(Map<String, String> env) {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = createDriver();
|
||||
instance.initialize(env);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
@@ -50,6 +58,8 @@ public abstract class Driver {
|
||||
return instance.driverDir().resolve(name);
|
||||
}
|
||||
|
||||
protected abstract void initialize(Map<String, String> env) throws Exception;
|
||||
|
||||
protected String cliFileName() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
"playwright.cmd" : "playwright.sh";
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<version>1.14.1</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>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"catalogs": {},
|
||||
"aliases": {
|
||||
"playwright": {
|
||||
"script-ref": "scripts/playwright.java",
|
||||
"description": "Playwright lets you automate Chromium, Firefox and Webkit with a single API. \nWith this cli you can install, trace, generate pdf and screenshots and more.\nExample on how to record and run a script:\n```\n jbang playwright@microsoft/playwright-java codegen -o Example.java`\n jbang --deps com.microsoft.playwright:playwright:RELEASE Example.java\n```"
|
||||
}
|
||||
},
|
||||
"templates": {}
|
||||
}
|
||||
+1
-4
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.14.1</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>
|
||||
|
||||
@@ -60,13 +60,24 @@ public interface Browser extends AutoCloseable {
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
* When using {@link Page#goto Page.goto()}, {@link Page#route Page.route()}, {@link Page#waitForURL Page.waitForURL()},
|
||||
* {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse Page.waitForResponse()} it takes the
|
||||
* base URL in consideration by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a>
|
||||
* constructor for building the corresponding URL. Examples:
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
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 +125,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 +137,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 +153,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()}.
|
||||
@@ -148,6 +174,12 @@ public interface Browser extends AutoCloseable {
|
||||
* state.
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
public Boolean strictSelectors;
|
||||
/**
|
||||
* Changes the timezone of the context. See <a
|
||||
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
|
||||
@@ -159,7 +191,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;
|
||||
|
||||
@@ -167,6 +199,10 @@ public interface Browser extends AutoCloseable {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setBypassCSP(boolean bypassCSP) {
|
||||
this.bypassCSP = bypassCSP;
|
||||
return this;
|
||||
@@ -251,6 +287,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;
|
||||
@@ -259,6 +306,10 @@ public interface Browser extends AutoCloseable {
|
||||
this.storageStatePath = storageStatePath;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setTimezoneId(String timezoneId) {
|
||||
this.timezoneId = timezoneId;
|
||||
return this;
|
||||
@@ -280,13 +331,24 @@ public interface Browser extends AutoCloseable {
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
* When using {@link Page#goto Page.goto()}, {@link Page#route Page.route()}, {@link Page#waitForURL Page.waitForURL()},
|
||||
* {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse Page.waitForResponse()} it takes the
|
||||
* base URL in consideration by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a>
|
||||
* constructor for building the corresponding URL. Examples:
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
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 +396,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 +408,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 +424,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()}.
|
||||
@@ -368,6 +445,12 @@ public interface Browser extends AutoCloseable {
|
||||
* state.
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
public Boolean strictSelectors;
|
||||
/**
|
||||
* Changes the timezone of the context. See <a
|
||||
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
|
||||
@@ -379,7 +462,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;
|
||||
|
||||
@@ -387,6 +470,10 @@ public interface Browser extends AutoCloseable {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setBypassCSP(boolean bypassCSP) {
|
||||
this.bypassCSP = bypassCSP;
|
||||
return this;
|
||||
@@ -471,6 +558,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;
|
||||
@@ -479,6 +577,10 @@ public interface Browser extends AutoCloseable {
|
||||
this.storageStatePath = storageStatePath;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setTimezoneId(String timezoneId) {
|
||||
this.timezoneId = timezoneId;
|
||||
return this;
|
||||
@@ -495,6 +597,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 +692,67 @@ public interface Browser extends AutoCloseable {
|
||||
* BrowserContext#newPage BrowserContext.newPage()} to control their exact life times.
|
||||
*/
|
||||
Page newPage(NewPageOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
* <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> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
* <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> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
* <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> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
*
|
||||
* <p> Returns the buffer with trace data.
|
||||
*/
|
||||
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,12 +537,27 @@ 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.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
|
||||
* options was provided and the passed URL is a path, it gets merged via the <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
|
||||
* @param handler handler function to route the request.
|
||||
*/
|
||||
void route(String url, Consumer<Route> handler);
|
||||
@@ -501,7 +565,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,12 +583,27 @@ 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.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
|
||||
* options was provided and the passed URL is a path, it gets merged via the <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
|
||||
* @param handler handler function to route the request.
|
||||
*/
|
||||
void route(Pattern url, Consumer<Route> handler);
|
||||
@@ -532,7 +611,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,12 +629,27 @@ 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.
|
||||
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the context
|
||||
* options was provided and the passed URL is a path, it gets merged via the <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
|
||||
* @param handler handler function to route the request.
|
||||
*/
|
||||
void route(Predicate<String> url, Consumer<Route> handler);
|
||||
@@ -625,6 +719,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 {
|
||||
/**
|
||||
@@ -218,17 +278,34 @@ public interface BrowserType {
|
||||
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
|
||||
*/
|
||||
public List<String> args;
|
||||
/**
|
||||
* When using {@link Page#goto Page.goto()}, {@link Page#route Page.route()}, {@link Page#waitForURL Page.waitForURL()},
|
||||
* {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse Page.waitForResponse()} it takes the
|
||||
* base URL in consideration by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a>
|
||||
* constructor for building the corresponding URL. Examples:
|
||||
* <ul>
|
||||
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
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 +328,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 +410,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,11 +426,26 @@ 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;
|
||||
/**
|
||||
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
|
||||
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
|
||||
* more about the strict mode.
|
||||
*/
|
||||
public Boolean strictSelectors;
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
@@ -362,12 +457,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;
|
||||
|
||||
@@ -379,10 +478,23 @@ public interface BrowserType {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setBypassCSP(boolean bypassCSP) {
|
||||
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,10 +619,25 @@ 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;
|
||||
}
|
||||
public LaunchPersistentContextOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -519,6 +646,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 +676,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.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.microsoft.playwright.impl.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@@ -28,7 +29,7 @@ import static java.util.Arrays.asList;
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Path driver = Driver.ensureDriverInstalled();
|
||||
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap());
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
pb.command().addAll(asList(args));
|
||||
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
|
||||
|
||||
@@ -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,12 @@ 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> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
|
||||
*
|
||||
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
|
||||
* <pre>{@code
|
||||
* // 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>
|
||||
@@ -47,26 +46,40 @@ import java.util.*;
|
||||
* has no access to the downloaded files.
|
||||
*/
|
||||
public interface Download {
|
||||
/**
|
||||
* Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations,
|
||||
* {@code download.failure()} would resolve to {@code "canceled"}.
|
||||
*/
|
||||
void cancel();
|
||||
/**
|
||||
* Returns readable stream for current download or {@code null} if download failed.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p> Note that the download's file name is a random GUID, use {@link Download#suggestedFilename Download.suggestedFilename()}
|
||||
* to get suggested file name.
|
||||
*/
|
||||
Path path();
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
|
||||
@@ -24,21 +24,8 @@ import java.util.*;
|
||||
* ElementHandle represents an in-page DOM element. ElementHandles can be created with the {@link Page#querySelector
|
||||
* Page.querySelector()} method.
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.*;
|
||||
*
|
||||
* public class Example {
|
||||
* public static void main(String[] args) {
|
||||
* try (Playwright playwright = Playwright.create()) {
|
||||
* BrowserType chromium = playwright.chromium();
|
||||
* Browser browser = chromium.launch();
|
||||
* Page page = browser.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* ElementHandle hrefElement = page.querySelector("a");
|
||||
* hrefElement.click();
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ElementHandle hrefElement = page.querySelector("a");
|
||||
* hrefElement.click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> ElementHandle prevents DOM element from garbage collection unless the handle is disposed with {@link JSHandle#dispose
|
||||
@@ -46,6 +33,30 @@ import java.util.*;
|
||||
*
|
||||
* <p> ElementHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()} and {@link
|
||||
* Page#evaluate Page.evaluate()} methods.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> In most cases, you would want to use the {@code Locator} object instead. You should only use {@code ElementHandle} if you want to
|
||||
* retain a handle to a particular DOM Node that you intend to pass into {@link Page#evaluate Page.evaluate()} as an
|
||||
* argument.
|
||||
*
|
||||
* <p> The difference between the {@code Locator} and ElementHandle is that the ElementHandle points to a particular element, while
|
||||
* {@code Locator} captures the logic of how to retrieve an element.
|
||||
*
|
||||
* <p> In the example below, handle points to a particular DOM element on page. If that element changes text or is used by
|
||||
* React to render an entirely different component, handle is still pointing to that very DOM element. This can lead to
|
||||
* unexpected behaviors.
|
||||
* <pre>{@code
|
||||
* ElementHandle handle = page.querySelector("text=Submit");
|
||||
* handle.hover();
|
||||
* handle.click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> With the locator, every time the {@code element} is used, up-to-date DOM element is located in the page using the selector. So
|
||||
* in the snippet below, underlying DOM element is going to be located twice.
|
||||
* <pre>{@code
|
||||
* Locator locator = page.locator("text=Submit");
|
||||
* locator.hover();
|
||||
* locator.click();
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ElementHandle extends JSHandle {
|
||||
class CheckOptions {
|
||||
@@ -60,12 +71,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 +97,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 +153,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 +195,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 +236,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,8 +274,17 @@ public interface ElementHandle extends JSHandle {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
public DblclickOptions setTrial(boolean trial) {
|
||||
this.trial = trial;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FillOptions {
|
||||
/**
|
||||
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
public Boolean force;
|
||||
/**
|
||||
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
|
||||
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
|
||||
@@ -240,6 +298,10 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public FillOptions setForce(boolean force) {
|
||||
this.force = force;
|
||||
return this;
|
||||
}
|
||||
public FillOptions setNoWaitAfter(boolean noWaitAfter) {
|
||||
this.noWaitAfter = noWaitAfter;
|
||||
return this;
|
||||
@@ -271,6 +333,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 +359,23 @@ public interface ElementHandle extends JSHandle {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
public HoverOptions setTrial(boolean trial) {
|
||||
this.trial = trial;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class InputValueOptions {
|
||||
/**
|
||||
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
|
||||
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
|
||||
* Page.setDefaultTimeout()} methods.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public InputValueOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class PressOptions {
|
||||
/**
|
||||
@@ -385,6 +470,11 @@ public interface ElementHandle extends JSHandle {
|
||||
}
|
||||
}
|
||||
class SelectOptionOptions {
|
||||
/**
|
||||
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
public Boolean force;
|
||||
/**
|
||||
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
|
||||
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
|
||||
@@ -398,6 +488,10 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public SelectOptionOptions setForce(boolean force) {
|
||||
this.force = force;
|
||||
return this;
|
||||
}
|
||||
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
|
||||
this.noWaitAfter = noWaitAfter;
|
||||
return this;
|
||||
@@ -408,6 +502,11 @@ public interface ElementHandle extends JSHandle {
|
||||
}
|
||||
}
|
||||
class SelectTextOptions {
|
||||
/**
|
||||
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability/">actionability</a> checks. Defaults to
|
||||
* {@code false}.
|
||||
*/
|
||||
public Boolean force;
|
||||
/**
|
||||
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
|
||||
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
|
||||
@@ -415,6 +514,10 @@ public interface ElementHandle extends JSHandle {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
public SelectTextOptions setForce(boolean force) {
|
||||
this.force = force;
|
||||
return this;
|
||||
}
|
||||
public SelectTextOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -471,6 +574,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 +604,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 +652,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 +678,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 +758,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 +779,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 +805,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 +823,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 +841,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 +862,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 +910,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 +1048,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 +1065,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 +1098,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 +1116,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);
|
||||
/**
|
||||
@@ -985,6 +1130,16 @@ public interface ElementHandle extends JSHandle {
|
||||
* Returns the {@code element.innerText}.
|
||||
*/
|
||||
String innerText();
|
||||
/**
|
||||
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
|
||||
*/
|
||||
default String inputValue() {
|
||||
return inputValue(null);
|
||||
}
|
||||
/**
|
||||
* Returns {@code input.value} for {@code <input>} or {@code <textarea>} or {@code <select>} element. Throws for non-input elements.
|
||||
*/
|
||||
String inputValue(InputValueOptions options);
|
||||
/**
|
||||
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
|
||||
*/
|
||||
@@ -1033,7 +1188,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 +1214,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 +1275,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 +1303,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 +1329,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 +1357,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 +1383,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 +1411,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 +1437,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 +1465,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 +1491,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 +1519,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 +1545,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 +1573,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 +1692,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 +1712,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 +1767,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 +1788,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);
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -40,12 +40,24 @@ import java.util.*;
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Playwright extends AutoCloseable {
|
||||
class CreateOptions {
|
||||
/**
|
||||
* Additional environment variables that will be passed to the driver process. By default driver process inherits
|
||||
* environment variables of the Playwright process.
|
||||
*/
|
||||
public Map<String, String> env;
|
||||
|
||||
public CreateOptions setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This object can be used to launch or connect to Chromium, returning instances of {@code 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 +66,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();
|
||||
/**
|
||||
@@ -72,8 +84,12 @@ public interface Playwright extends AutoCloseable {
|
||||
* playwright.close();
|
||||
* }</pre>
|
||||
*/
|
||||
static Playwright create(CreateOptions options) {
|
||||
return PlaywrightImpl.create(options);
|
||||
}
|
||||
|
||||
static Playwright create() {
|
||||
return PlaywrightImpl.create();
|
||||
return create(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -46,6 +47,14 @@ public interface Response {
|
||||
* Returns the matching {@code Request} object.
|
||||
*/
|
||||
Request request();
|
||||
/**
|
||||
* Returns SSL and other security information.
|
||||
*/
|
||||
SecurityDetails securityDetails();
|
||||
/**
|
||||
* Returns the IP address and port of the server.
|
||||
*/
|
||||
ServerAddr serverAddr();
|
||||
/**
|
||||
* Contains the status code of the response (e.g., 200 for a success).
|
||||
*/
|
||||
|
||||
@@ -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.navigate("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.navigate("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.navigate("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,79 @@
|
||||
/*
|
||||
* 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 cancel() {
|
||||
sendMessage("cancel");
|
||||
}
|
||||
|
||||
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,20 @@ 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.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
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 +43,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;
|
||||
@@ -47,10 +55,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
PAGE,
|
||||
REQUEST,
|
||||
REQUESTFAILED,
|
||||
REQUESTFINISHED,
|
||||
RESPONSE,
|
||||
}
|
||||
|
||||
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -60,6 +73,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} else {
|
||||
browser = null;
|
||||
}
|
||||
this.tracing = new TracingImpl(this);
|
||||
}
|
||||
|
||||
void setBaseUrl(String spec) {
|
||||
try {
|
||||
this.baseUrl = new URL(spec);
|
||||
} catch (MalformedURLException e) {
|
||||
this.baseUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,6 +104,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 +154,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 +171,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 +243,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 +291,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));
|
||||
@@ -252,7 +318,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
route(new UrlMatcher(this.baseUrl, url), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -344,9 +410,14 @@ 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);
|
||||
unroute(new UrlMatcher(this.baseUrl, url), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -395,14 +466,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) {
|
||||
@@ -173,6 +171,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
}
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -182,6 +183,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();
|
||||
}
|
||||
@@ -147,6 +189,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
}
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,20 @@ class ChannelOwner extends LoggingSupport {
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
<T> T withWaitLogging(String apiName, Supplier<T> code) {
|
||||
return new WaitForEventLogger<>(this, apiName, code).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
<T> T withLogging(String apiName, Supplier<T> code) {
|
||||
String previousApiName = connection.setApiName(apiName);
|
||||
try {
|
||||
return super.withLogging(apiName, code);
|
||||
} finally {
|
||||
connection.setApiName(previousApiName);
|
||||
}
|
||||
}
|
||||
|
||||
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
|
||||
return connection.sendMessageAsync(guid, method, params);
|
||||
}
|
||||
|
||||
@@ -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,12 @@ public class Connection {
|
||||
private int lastId = 0;
|
||||
private final Path srcDir;
|
||||
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
|
||||
private String apiName;
|
||||
private static final boolean isLogging;
|
||||
static {
|
||||
String debug = System.getenv("DEBUG");
|
||||
isLogging = (debug != null) && debug.contains("pw:channel");
|
||||
}
|
||||
|
||||
class Root extends ChannelOwner {
|
||||
Root(Connection connection) {
|
||||
@@ -88,6 +95,12 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
String previous = apiName;
|
||||
apiName = name;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
transport.close();
|
||||
}
|
||||
@@ -148,12 +161,19 @@ public class Connection {
|
||||
message.addProperty("guid", guid);
|
||||
message.addProperty("method", method);
|
||||
message.add("params", params);
|
||||
JsonObject metadata = new JsonObject();
|
||||
if (srcDir != null) {
|
||||
JsonObject metadata = new JsonObject();
|
||||
metadata.add("stack", currentStackTrace());
|
||||
message.add("metadata", metadata);
|
||||
}
|
||||
transport.send(gson().toJson(message));
|
||||
if (apiName != null) {
|
||||
metadata.addProperty("apiName", apiName);
|
||||
}
|
||||
message.add("metadata", metadata);
|
||||
String messageString = gson().toJson(message);
|
||||
if (isLogging) {
|
||||
logWithTimestamp("SEND ► " + messageString);
|
||||
}
|
||||
transport.send(messageString);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -184,6 +204,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 +278,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 +299,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 +320,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 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
|
||||
@@ -49,60 +44,38 @@ public class DownloadImpl extends ChannelOwner implements Download {
|
||||
return initializer.get("suggestedFilename").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
page.withLogging("Download.cancel", () -> artifact.cancel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream createReadStream() {
|
||||
return withLogging("Download.createReadStream", () -> {
|
||||
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 page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
withLogging("Download.delete", () -> {
|
||||
sendMessage("delete");
|
||||
});
|
||||
page.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 page.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 page.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);
|
||||
});
|
||||
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,10 +209,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
|
||||
@Override
|
||||
public void hover(HoverOptions options) {
|
||||
withLogging("ElementHandle.hover", () -> {
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
sendMessage("hover", params);
|
||||
});
|
||||
withLogging("ElementHandle.hover", () -> hoverImpl(options));
|
||||
}
|
||||
|
||||
private void hoverImpl(HoverOptions options) {
|
||||
if (options == null) {
|
||||
options = new HoverOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
sendMessage("hover", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,6 +236,20 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(InputValueOptions options) {
|
||||
return withLogging("ElementHandle.inputValue", () -> inputValueImpl(options));
|
||||
}
|
||||
|
||||
private String inputValueImpl(InputValueOptions options) {
|
||||
if (options == null) {
|
||||
options = new InputValueOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
|
||||
return json.get("value").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return withLogging("ElementHandle.isChecked", () -> {
|
||||
|
||||
@@ -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.*;
|
||||
@@ -39,6 +41,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
FrameImpl parentFrame;
|
||||
Set<FrameImpl> childFrames = new LinkedHashSet<>();
|
||||
private final Set<LoadState> loadStates = new HashSet<>();
|
||||
|
||||
enum InternalEventType { NAVIGATED, LOADSTATE }
|
||||
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
|
||||
PageImpl page;
|
||||
@@ -68,12 +71,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle querySelector(String selector) {
|
||||
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector));
|
||||
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
|
||||
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
|
||||
}
|
||||
|
||||
ElementHandleImpl querySelectorImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
|
||||
if (options == null) {
|
||||
options = new QuerySelectorOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
JsonElement json = sendMessage("querySelector", params);
|
||||
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
|
||||
@@ -131,12 +137,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
|
||||
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg));
|
||||
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
|
||||
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options));
|
||||
}
|
||||
|
||||
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg) {
|
||||
JsonObject params = new JsonObject();
|
||||
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
|
||||
if (options == null) {
|
||||
options = new EvalOnSelectorOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
params.addProperty("expression", pageFunction);
|
||||
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
|
||||
@@ -405,6 +414,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
sendMessage("hover", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
|
||||
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options));
|
||||
}
|
||||
|
||||
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
|
||||
if (options == null) {
|
||||
options = new DragAndDropOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("source", source);
|
||||
params.addProperty("target", target);
|
||||
sendMessage("dragAndDrop", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(String selector, InnerHTMLOptions options) {
|
||||
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
|
||||
@@ -435,6 +459,21 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return json.get("value").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(String selector, InputValueOptions options) {
|
||||
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options));
|
||||
}
|
||||
|
||||
String inputValueImpl(String selector, InputValueOptions options) {
|
||||
if (options == null) {
|
||||
options = new InputValueOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
|
||||
return json.get("value").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(String selector, IsCheckedOptions options) {
|
||||
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
|
||||
@@ -520,6 +559,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector) {
|
||||
return new LocatorImpl(this, selector);
|
||||
}
|
||||
|
||||
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
@@ -737,7 +781,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withLogging("Frame.waitForLoadState", () -> waitForLoadStateImpl(state, options));
|
||||
withWaitLogging("Frame.waitForLoadState", () -> {
|
||||
waitForLoadStateImpl(state, options);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
|
||||
@@ -856,10 +903,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 +919,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(page.context().baseUrl, options.url);
|
||||
}
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
@@ -910,6 +963,37 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForURL(String url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(page.context().baseUrl, 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");
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.microsoft.playwright.Keyboard;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
class KeyboardImpl implements Keyboard {
|
||||
private final ChannelOwner page;
|
||||
|
||||
KeyboardImpl(ChannelOwner page) {
|
||||
@@ -30,7 +30,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
|
||||
@Override
|
||||
public void down(String key) {
|
||||
withLogging("Keyboard.down", () -> {
|
||||
page.withLogging("Keyboard.down", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("key", key);
|
||||
page.sendMessage("keyboardDown", params);
|
||||
@@ -39,7 +39,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
|
||||
@Override
|
||||
public void insertText(String text) {
|
||||
withLogging("Keyboard.insertText", () -> {
|
||||
page.withLogging("Keyboard.insertText", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("text", text);
|
||||
page.sendMessage("keyboardInsertText", params);
|
||||
@@ -48,7 +48,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
|
||||
@Override
|
||||
public void press(String key, PressOptions options) {
|
||||
withLogging("Keyboard.press", () -> pressImpl(key, options));
|
||||
page.withLogging("Keyboard.press", () -> pressImpl(key, options));
|
||||
}
|
||||
|
||||
private void pressImpl(String key, PressOptions options) {
|
||||
@@ -62,7 +62,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
|
||||
@Override
|
||||
public void type(String text, TypeOptions options) {
|
||||
withLogging("Keyboard.type", () -> typeImpl(text, options));
|
||||
page.withLogging("Keyboard.type", () -> typeImpl(text, options));
|
||||
}
|
||||
|
||||
private void typeImpl(String text, TypeOptions options) {
|
||||
@@ -76,7 +76,7 @@ class KeyboardImpl extends LoggingSupport implements Keyboard {
|
||||
|
||||
@Override
|
||||
public void up(String key) {
|
||||
withLogging("Keyboard.up", () -> {
|
||||
page.withLogging("Keyboard.up", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("key", key);
|
||||
page.sendMessage("keyboardUp", params);
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.ElementHandle;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.JSHandle;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.SelectOption;
|
||||
import com.microsoft.playwright.options.WaitForSelectorState;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
class LocatorImpl implements Locator {
|
||||
private final FrameImpl frame;
|
||||
private final String selector;
|
||||
|
||||
public LocatorImpl(FrameImpl frame, String selector) {
|
||||
this.frame = frame;
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
|
||||
ElementHandleOptions handleOptions = convertViaJson(options, ElementHandleOptions.class);
|
||||
// TODO: support deadline based timeout
|
||||
// Double timeout = null;
|
||||
// if (handleOptions != null) {
|
||||
// timeout = handleOptions.timeout;
|
||||
// }
|
||||
// timeout = frame.page.timeoutSettings.timeout(timeout);
|
||||
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
|
||||
ElementHandle handle = elementHandle(handleOptions);
|
||||
try {
|
||||
return callback.apply(handle, options);
|
||||
} finally {
|
||||
if (handle != null) {
|
||||
handle.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> allInnerTexts() {
|
||||
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.innerText)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> allTextContents() {
|
||||
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.textContent || '')");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingBox boundingBox(BoundingBoxOptions options) {
|
||||
return withElement((h, o) -> h.boundingBox(), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(CheckOptions options) {
|
||||
if (options == null) {
|
||||
options = new CheckOptions();
|
||||
}
|
||||
frame.check(selector, convertViaJson(options, Frame.CheckOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(ClickOptions options) {
|
||||
if (options == null) {
|
||||
options = new ClickOptions();
|
||||
}
|
||||
frame.click(selector, convertViaJson(options, Frame.ClickOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return ((Number) evaluateAll("ee => ee.length")).intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dblclick(DblclickOptions options) {
|
||||
if (options == null) {
|
||||
options = new DblclickOptions();
|
||||
}
|
||||
frame.dblclick(selector, convertViaJson(options, Frame.DblclickOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(String type, Object eventInit, DispatchEventOptions options) {
|
||||
if (options == null) {
|
||||
options = new DispatchEventOptions();
|
||||
}
|
||||
frame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle elementHandle(ElementHandleOptions options) {
|
||||
if (options == null) {
|
||||
options = new ElementHandleOptions();
|
||||
}
|
||||
Frame.WaitForSelectorOptions frameOptions = convertViaJson(options, Frame.WaitForSelectorOptions.class);
|
||||
frameOptions.setStrict(true);
|
||||
frameOptions.setState(WaitForSelectorState.ATTACHED);
|
||||
return frame.waitForSelector(selector, frameOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ElementHandle> elementHandles() {
|
||||
return frame.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(String expression, Object arg, EvaluateOptions options) {
|
||||
return withElement((h, o) -> h.evaluate(expression, arg), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluateAll(String expression, Object arg) {
|
||||
return frame.evalOnSelectorAll(selector, expression, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
|
||||
return withElement((h, o) -> h.evaluateHandle(expression, arg), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(String value, FillOptions options) {
|
||||
if (options == null) {
|
||||
options = new FillOptions();
|
||||
}
|
||||
frame.fill(selector, value, convertViaJson(options, Frame.FillOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator first() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus(FocusOptions options) {
|
||||
if (options == null) {
|
||||
options = new FocusOptions();
|
||||
}
|
||||
frame.focus(selector, convertViaJson(options, Frame.FocusOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name, GetAttributeOptions options) {
|
||||
if (options == null) {
|
||||
options = new GetAttributeOptions();
|
||||
}
|
||||
return frame.getAttribute(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hover(HoverOptions options) {
|
||||
if (options == null) {
|
||||
options = new HoverOptions();
|
||||
}
|
||||
frame.hover(selector, convertViaJson(options, Frame.HoverOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(InnerHTMLOptions options) {
|
||||
if (options == null) {
|
||||
options = new InnerHTMLOptions();
|
||||
}
|
||||
return frame.innerHTML(selector, convertViaJson(options, Frame.InnerHTMLOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerText(InnerTextOptions options) {
|
||||
if (options == null) {
|
||||
options = new InnerTextOptions();
|
||||
}
|
||||
return frame.innerText(selector, convertViaJson(options, Frame.InnerTextOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(InputValueOptions options) {
|
||||
if (options == null) {
|
||||
options = new InputValueOptions();
|
||||
}
|
||||
return frame.inputValue(selector, convertViaJson(options, Frame.InputValueOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(IsCheckedOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsCheckedOptions();
|
||||
}
|
||||
return frame.isChecked(selector, convertViaJson(options, Frame.IsCheckedOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled(IsDisabledOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsDisabledOptions();
|
||||
}
|
||||
return frame.isDisabled(selector, convertViaJson(options, Frame.IsDisabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(IsEditableOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsEditableOptions();
|
||||
}
|
||||
return frame.isEditable(selector, convertViaJson(options, Frame.IsEditableOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(IsEnabledOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsEnabledOptions();
|
||||
}
|
||||
return frame.isEnabled(selector, convertViaJson(options, Frame.IsEnabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden(IsHiddenOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsHiddenOptions();
|
||||
}
|
||||
return frame.isHidden(selector, convertViaJson(options, Frame.IsHiddenOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible(IsVisibleOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
}
|
||||
return frame.isVisible(selector, convertViaJson(options, Frame.IsVisibleOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator last() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector) {
|
||||
return new LocatorImpl(frame, this.selector + " >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator nth(int index) {
|
||||
return new LocatorImpl(frame, selector + " >> nth=" + index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void press(String key, PressOptions options) {
|
||||
if (options == null) {
|
||||
options = new PressOptions();
|
||||
}
|
||||
frame.press(selector, key, convertViaJson(options, Frame.PressOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] screenshot(ScreenshotOptions options) {
|
||||
return withElement((h, o) -> h.screenshot(o), convertViaJson(options, ElementHandle.ScreenshotOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
|
||||
withElement((h, o) -> {
|
||||
h.scrollIntoViewIfNeeded(o);
|
||||
return null;
|
||||
}, convertViaJson(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(ElementHandle values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String[] values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(SelectOption values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(ElementHandle[] values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectText(SelectTextOptions options) {
|
||||
withElement((h, o) -> {
|
||||
h.selectText(o);
|
||||
return null;
|
||||
}, convertViaJson(options, ElementHandle.SelectTextOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tap(TapOptions options) {
|
||||
if (options == null) {
|
||||
options = new TapOptions();
|
||||
}
|
||||
frame.tap(selector, convertViaJson(options, Frame.TapOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textContent(TextContentOptions options) {
|
||||
if (options == null) {
|
||||
options = new TextContentOptions();
|
||||
}
|
||||
return frame.textContent(selector, convertViaJson(options, Frame.TextContentOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void type(String text, TypeOptions options) {
|
||||
if (options == null) {
|
||||
options = new TypeOptions();
|
||||
}
|
||||
frame.type(selector, text, convertViaJson(options, Frame.TypeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncheck(UncheckOptions options) {
|
||||
if (options == null) {
|
||||
options = new UncheckOptions();
|
||||
}
|
||||
frame.uncheck(selector, convertViaJson(options, Frame.UncheckOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Locator@" + selector;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -527,8 +548,9 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle querySelector(String selector) {
|
||||
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(selector));
|
||||
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
|
||||
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
|
||||
selector, convertViaJson(options, Frame.QuerySelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -537,8 +559,9 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
|
||||
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(selector, pageFunction, arg));
|
||||
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
|
||||
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
|
||||
selector, pageFunction, arg, convertViaJson(options, Frame.EvalOnSelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -695,7 +718,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Frame frameByUrl(String glob) {
|
||||
return frameFor(new UrlMatcher(glob));
|
||||
return frameFor(new UrlMatcher(browserContext.baseUrl, glob));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -774,6 +797,12 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
|
||||
withLogging("Page.dragAndDrop", () ->
|
||||
mainFrame.dragAndDropImpl(source, target, convertViaJson(options, Frame.DragAndDropOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(String selector, InnerHTMLOptions options) {
|
||||
return withLogging("Page.innerHTML",
|
||||
@@ -786,6 +815,12 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(String selector, InputValueOptions options) {
|
||||
return withLogging("Page.inputValue",
|
||||
() -> mainFrame.inputValueImpl(selector, convertViaJson(options, Frame.InputValueOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(String selector, IsCheckedOptions options) {
|
||||
return withLogging("Page.isChecked",
|
||||
@@ -832,6 +867,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector) {
|
||||
return mainFrame.locator(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame mainFrame() {
|
||||
return mainFrame;
|
||||
@@ -843,14 +883,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
|
||||
@@ -907,7 +944,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
route(new UrlMatcher(browserContext.baseUrl, url), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1115,7 +1152,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public void unroute(String url, Consumer<Route> handler) {
|
||||
unroute(new UrlMatcher(url), handler);
|
||||
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1144,20 +1181,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
|
||||
@@ -1181,8 +1221,10 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withLogging("Page.waitForLoadState",
|
||||
() -> mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class)));
|
||||
withWaitLogging("Page.waitForLoadState", () -> {
|
||||
mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1249,7 +1291,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
|
||||
return waitForRequest(toRequestPredicate(new UrlMatcher(urlGlob)), options, code);
|
||||
return waitForRequest(toRequestPredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1259,7 +1301,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,9 +1320,27 @@ 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);
|
||||
return waitForResponse(toResponsePredicate(new UrlMatcher(browserContext.baseUrl, urlGlob)), options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1320,6 +1380,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(browserContext.baseUrl, 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) {
|
||||
|
||||
@@ -23,21 +23,28 @@ import com.microsoft.playwright.Selectors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private Process driverProcess;
|
||||
|
||||
public static PlaywrightImpl create() {
|
||||
public static PlaywrightImpl create(CreateOptions options) {
|
||||
try {
|
||||
Path driver = Driver.ensureDriverInstalled();
|
||||
Map<String, String> env = Collections.emptyMap();
|
||||
if (options != null && options.env != null) {
|
||||
env = options.env;
|
||||
}
|
||||
Path driver = Driver.ensureDriverInstalled(env);
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
// pb.environment().put("DEBUG", "pw:pro*");
|
||||
pb.environment().putAll(env);
|
||||
Process p = pb.start();
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
|
||||
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
|
||||
result.driverProcess = p;
|
||||
result.initSharedSelectors(null);
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to launch driver", e);
|
||||
@@ -47,15 +54,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
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Response;
|
||||
import com.microsoft.playwright.options.SecurityDetails;
|
||||
import com.microsoft.playwright.options.ServerAddr;
|
||||
import com.microsoft.playwright.options.Timing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -28,6 +30,8 @@ import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
public class ResponseImpl extends ChannelOwner implements Response {
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
private final RequestImpl request;
|
||||
@@ -46,7 +50,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
JsonObject item = e.getAsJsonObject();
|
||||
request.headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
|
||||
}
|
||||
request.timing = Serialization.gson().fromJson(initializer.get("timing"), Timing.class);
|
||||
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +92,28 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityDetails securityDetails() {
|
||||
return withLogging("Response.securityDetails", () -> {
|
||||
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
|
||||
if (json.has("value")) {
|
||||
return gson().fromJson(json.get("value"), SecurityDetails.class);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerAddr serverAddr() {
|
||||
return withLogging("Response.serverAddr", () -> {
|
||||
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
|
||||
if (json.has("value")) {
|
||||
return gson().fromJson(json.get("value"), ServerAddr.class);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int status() {
|
||||
return initializer.get("status").getAsInt();
|
||||
|
||||
@@ -37,7 +37,7 @@ class Router {
|
||||
}
|
||||
|
||||
void add(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
routes.add(new RouteInfo(matcher, handler));
|
||||
routes.add(0, new RouteInfo(matcher, handler));
|
||||
}
|
||||
|
||||
void remove(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -45,6 +45,9 @@ public class Stream extends ChannelOwner {
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) {
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("size", len);
|
||||
JsonObject json = sendMessage("read", params).getAsJsonObject();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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());
|
||||
// In case of CDP connection browser is null but since the connection is established by
|
||||
// the driver it is safe to consider the artifact local.
|
||||
if (context.browser() != null && context.browser().isRemote) {
|
||||
artifact.isRemote = true;
|
||||
}
|
||||
artifact.saveAs(path);
|
||||
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", () -> {
|
||||
if (options != null && options.path != null) {
|
||||
export(options.path);
|
||||
}
|
||||
context.sendMessage("tracingStop");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -33,15 +35,15 @@ class UrlMatcher {
|
||||
}
|
||||
|
||||
static UrlMatcher any() {
|
||||
return new UrlMatcher(null, null);
|
||||
return new UrlMatcher((Object) null, null);
|
||||
}
|
||||
|
||||
static UrlMatcher forOneOf(Object object) {
|
||||
static UrlMatcher forOneOf(URL baseUrl, Object object) {
|
||||
if (object == null) {
|
||||
return UrlMatcher.any();
|
||||
}
|
||||
if (object instanceof String) {
|
||||
return new UrlMatcher((String) object);
|
||||
return new UrlMatcher(baseUrl, (String) object);
|
||||
}
|
||||
if (object instanceof Pattern) {
|
||||
return new UrlMatcher((Pattern) object);
|
||||
@@ -52,8 +54,19 @@ class UrlMatcher {
|
||||
throw new PlaywrightException("Url must be String, Pattern or Predicate<String>, found: " + object.getClass().getTypeName());
|
||||
}
|
||||
|
||||
UrlMatcher(String url) {
|
||||
this(url, toPredicate(Pattern.compile(globToRegex(url))).or(s -> url == null || url.equals(s)));
|
||||
private static String resolveUrl(URL baseUrl, String spec) {
|
||||
if (baseUrl == null) {
|
||||
return spec;
|
||||
}
|
||||
try {
|
||||
return new URL(baseUrl, spec).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
|
||||
UrlMatcher(URL base, String url) {
|
||||
this(url, toPredicate(Pattern.compile(globToRegex(resolveUrl(base, url)))).or(s -> url == null || url.equals(s)));
|
||||
}
|
||||
|
||||
UrlMatcher(Pattern pattern) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class VideoImpl 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() {
|
||||
page.withLogging("Video.delete", () -> {
|
||||
try {
|
||||
waitForArtifact().delete();
|
||||
} catch (PlaywrightException e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path path() {
|
||||
while (fullPath == null) {
|
||||
page.connection.processOneMessage();
|
||||
}
|
||||
return fullPath;
|
||||
return page.withLogging("Video.path", () -> {
|
||||
if (isRemote) {
|
||||
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
|
||||
}
|
||||
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) {
|
||||
page.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("event", "");
|
||||
info.addProperty("waitId", waitId);
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("info", info);
|
||||
channel.withLogging(apiName, () -> channel.sendMessageAsync("waitForEventInfo", params));
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
@@ -86,18 +87,26 @@ 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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,9 +141,10 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
|
||||
List<Waitable<T>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType));
|
||||
private WebSocketFrame waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<WebSocketFrame> predicate, Double timeout) {
|
||||
List<Waitable<WebSocketFrame>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType,
|
||||
frame -> predicate == null || predicate.test(frame)));
|
||||
waitables.add(new WaitableWebSocketClose<>());
|
||||
waitables.add(new WaitableWebSocketError<>());
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
@@ -168,14 +178,22 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
switch (event) {
|
||||
case "frameSent": {
|
||||
int opCode = parameters.get("opcode").getAsInt();
|
||||
if (opCode != 1 && opCode != 2) {
|
||||
break;
|
||||
}
|
||||
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
|
||||
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
|
||||
parameters.get("data").getAsString(), opCode == 2);
|
||||
listeners.notify(EventType.FRAMESENT, WebSocketFrame);
|
||||
break;
|
||||
}
|
||||
case "frameReceived": {
|
||||
int opCode = parameters.get("opcode").getAsInt();
|
||||
if (opCode != 1 && opCode != 2) {
|
||||
break;
|
||||
}
|
||||
WebSocketFrameImpl WebSocketFrame = new WebSocketFrameImpl(
|
||||
parameters.get("data").getAsString(), parameters.get("opcode").getAsInt() == 2);
|
||||
parameters.get("data").getAsString(), opCode == 2);
|
||||
listeners.notify(EventType.FRAMERECEIVED, WebSocketFrame);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class SecurityDetails {
|
||||
/**
|
||||
* Common Name component of the Issuer field. from the certificate. This should only be used for informational purposes.
|
||||
* Optional.
|
||||
*/
|
||||
public String issuer;
|
||||
/**
|
||||
* The specific TLS protocol used. (e.g. {@code TLS 1.3}). Optional.
|
||||
*/
|
||||
public String protocol;
|
||||
/**
|
||||
* Common Name component of the Subject field from the certificate. This should only be used for informational purposes.
|
||||
* Optional.
|
||||
*/
|
||||
public String subjectName;
|
||||
/**
|
||||
* Unix timestamp (in seconds) specifying when this cert becomes valid. Optional.
|
||||
*/
|
||||
public Double validFrom;
|
||||
/**
|
||||
* Unix timestamp (in seconds) specifying when this cert becomes invalid. Optional.
|
||||
*/
|
||||
public Double validTo;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class ServerAddr {
|
||||
/**
|
||||
* IPv4 or IPV6 address of the server.
|
||||
*/
|
||||
public String ipAddress;
|
||||
public int port;
|
||||
|
||||
}
|
||||
@@ -189,6 +189,9 @@ public class Server implements HttpHandler {
|
||||
if (csp.containsKey(path)) {
|
||||
exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path));
|
||||
}
|
||||
if ("/".equals(path)) {
|
||||
path = "/index.html";
|
||||
}
|
||||
File file = new File(resourcesDir, path.substring(1));
|
||||
if (!file.exists()) {
|
||||
exchange.sendResponseHeaders(404, 0);
|
||||
|
||||
@@ -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,31 @@
|
||||
|
||||
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 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 +49,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 +96,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 +114,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static com.microsoft.playwright.Utils.assertJsonEquals;
|
||||
import static com.microsoft.playwright.Utils.getOS;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
@@ -217,7 +218,7 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
"}]", cookies);
|
||||
}
|
||||
|
||||
@@ -236,7 +237,7 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
"}]", cookies);
|
||||
assertEquals("gridcookie=GRID", page.evaluate("document.cookie"));
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
@@ -309,7 +310,7 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: true,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
"}]", context.cookies("https://www.example.com"));
|
||||
}
|
||||
|
||||
@@ -345,7 +346,7 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
"}", server.CROSS_PROCESS_PREFIX + "/grid.html");
|
||||
page.frames().get(1).evaluate("document.cookie = 'username=John Doe'");
|
||||
page.waitForTimeout(2000);
|
||||
boolean allowsThirdParty = isChromium() || isFirefox();
|
||||
boolean allowsThirdParty = isFirefox();
|
||||
List<Cookie> cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html");
|
||||
if (allowsThirdParty) {
|
||||
assertJsonEquals("[{\n" +
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class TestBrowserContextBaseUrl extends TestBase {
|
||||
@Test
|
||||
void shouldConstructANewURLWhenABaseURLInBrowserNewContextIsPassedToPageGoto() throws MalformedURLException {
|
||||
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) {
|
||||
Page page = context.newPage();
|
||||
assertEquals(server.EMPTY_PAGE, page.navigate("/empty.html").url());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConstructANewURLWhenABaseURLInBrowserNewPageIsPassedToPageGoto() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX))) {
|
||||
assertEquals(server.EMPTY_PAGE, page.navigate("/empty.html").url());
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void shouldConstructTheURLsCorrectlyWhenABaseURLWithoutATrailingSlashInBrowserNewPageIsPassedToPageGoto() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction"))) {
|
||||
assertEquals(server.PREFIX + "/mypage.html", page.navigate("mypage.html").url());
|
||||
assertEquals(server.PREFIX + "/mypage.html", page.navigate("./mypage.html").url());
|
||||
assertEquals(server.PREFIX + "/mypage.html", page.navigate("/mypage.html").url());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConstructTheURLsCorrectlyWhenABaseURLWithATrailingSlashInBrowserNewPageIsPassedToPageGoto() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction/"))) {
|
||||
assertEquals(server.PREFIX + "/url-construction/mypage.html", page.navigate("mypage.html").url());
|
||||
assertEquals(server.PREFIX + "/url-construction/mypage.html", page.navigate("./mypage.html").url());
|
||||
assertEquals(server.PREFIX + "/mypage.html", page.navigate("/mypage.html").url());
|
||||
assertEquals(server.PREFIX + "/url-construction/", page.navigate(".").url());
|
||||
assertEquals(server.PREFIX + "/", page.navigate("/").url());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotConstructANewURLWhenValidURLsArePassed() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL("http://microsoft.com"))) {
|
||||
assertEquals(server.EMPTY_PAGE, page.navigate(server.EMPTY_PAGE).url());
|
||||
|
||||
page.navigate("data:text/html,Hello world");
|
||||
assertEquals("data:text/html,Hello world", page.evaluate("window.location.href"));
|
||||
|
||||
page.navigate("about:blank");
|
||||
assertEquals("about:blank", page.evaluate("window.location.href"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeAbleToMatchAURLRelativeToItsGivenURLWithUrlMatcher() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/foobar/"))) {
|
||||
page.navigate("/kek/index.html");
|
||||
page.waitForURL("/kek/index.html");
|
||||
assertEquals(server.PREFIX + "/kek/index.html", page.url());
|
||||
|
||||
page.route("./kek/index.html", route -> route.fulfill(new Route.FulfillOptions().setBody("base-url-matched-route")));
|
||||
Request[] request = {null};
|
||||
Response response = page.waitForResponse("./kek/index.html", () -> {
|
||||
request[0] = page.waitForRequest("./kek/index.html", () -> {
|
||||
page.navigate("./kek/index.html");
|
||||
});
|
||||
});
|
||||
assertNotNull(request[0]);
|
||||
assertNotNull(response);
|
||||
assertEquals(server.PREFIX + "/foobar/kek/index.html", request[0].url());
|
||||
assertEquals(server.PREFIX + "/foobar/kek/index.html", response.url());
|
||||
assertEquals("base-url-matched-route", response.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -48,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" }]", cookies);
|
||||
}
|
||||
|
||||
@@ -72,7 +74,11 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
assertEquals(timestamp, cookie.expires);
|
||||
assertEquals(false, cookie.httpOnly);
|
||||
assertEquals(false, cookie.secure);
|
||||
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
|
||||
if (isChromium()) {
|
||||
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
|
||||
} else {
|
||||
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -140,7 +146,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" name: 'username',\n" +
|
||||
@@ -150,7 +156,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" }\n" +
|
||||
"]", cookies);
|
||||
}
|
||||
@@ -168,19 +174,47 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" value: 'tweets',\n" +
|
||||
" domain: 'baz.com',\n" +
|
||||
" path: '/',\n" +
|
||||
" expires: -1,\n" +
|
||||
" expires: -1.0,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: true,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
"}, {\n" +
|
||||
" name: 'doggo',\n" +
|
||||
" value: 'woofs',\n" +
|
||||
" domain: 'foo.com',\n" +
|
||||
" path: '/',\n" +
|
||||
" expires: -1,\n" +
|
||||
" expires: -1.0,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: true,\n" +
|
||||
" sameSite: 'NONE'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
"}]", cookies);
|
||||
}
|
||||
|
||||
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('; ')");
|
||||
if (isChromium()) {
|
||||
assertEquals("one=uno; two=dos", documentCookie);
|
||||
} else {
|
||||
assertEquals("one=uno; three=tres; two=dos", documentCookie);
|
||||
}
|
||||
|
||||
List<SameSiteAttribute> list = context.cookies().stream().map(c -> c.sameSite).sorted().collect(Collectors.toList());
|
||||
if (isChromium()) {
|
||||
assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX), list);
|
||||
} else {
|
||||
assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX, SameSiteAttribute.NONE), list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+93
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,11 +57,10 @@ public class TestBrowserContextRoute extends TestBase {
|
||||
Page page = context.newPage();
|
||||
|
||||
List<Integer> intercepted = new ArrayList<>();
|
||||
Consumer<Route> handler1 = route -> {
|
||||
context.route("**/*", route -> {
|
||||
intercepted.add(1);
|
||||
route.resume();
|
||||
};
|
||||
context.route("**/empty.html", handler1);
|
||||
});
|
||||
context.route("**/empty.html", route -> {
|
||||
intercepted.add(2);
|
||||
route.resume();
|
||||
@@ -70,22 +69,23 @@ public class TestBrowserContextRoute extends TestBase {
|
||||
intercepted.add(3);
|
||||
route.resume();
|
||||
});
|
||||
context.route("**/*", route -> {
|
||||
Consumer<Route> handler4 = route -> {
|
||||
intercepted.add(4);
|
||||
route.resume();
|
||||
});
|
||||
};
|
||||
context.route("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(1), intercepted);
|
||||
assertEquals(asList(4), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
context.unroute("**/empty.html", handler1);
|
||||
context.unroute("**/empty.html", handler4);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(2), intercepted);
|
||||
assertEquals(asList(3), intercepted);
|
||||
|
||||
intercepted.clear();
|
||||
context.unroute("**/empty.html");
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(asList(4), intercepted);
|
||||
assertEquals(asList(1), intercepted);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
+14
-14
@@ -100,24 +100,24 @@ public class TestBrowserContextStorageState extends TestBase {
|
||||
context.storageState(new BrowserContext.StorageStateOptions().setPath(path));
|
||||
JsonObject expected = new Gson().fromJson(
|
||||
"{\n" +
|
||||
" \"cookies\":[\n" +
|
||||
" 'cookies':[\n" +
|
||||
" { \n" +
|
||||
" \"name\":\"username\",\n" +
|
||||
" \"value\":\"John Doe\",\n" +
|
||||
" \"domain\":\"www.example.com\",\n" +
|
||||
" \"path\":\"/\",\n" +
|
||||
" \"expires\":-1,\n" +
|
||||
" \"httpOnly\":false,\n" +
|
||||
" \"secure\":false,\n" +
|
||||
" \"sameSite\":\"None\"\n" +
|
||||
" 'name':'username',\n" +
|
||||
" 'value':'John Doe',\n" +
|
||||
" 'domain':'www.example.com',\n" +
|
||||
" 'path':'/',\n" +
|
||||
" 'expires':-1,\n" +
|
||||
" 'httpOnly':false,\n" +
|
||||
" 'secure':false,\n" +
|
||||
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
|
||||
" }],\n" +
|
||||
" \"origins\":[\n" +
|
||||
" 'origins':[\n" +
|
||||
" {\n" +
|
||||
" \"origin\":\"https://www.example.com\",\n" +
|
||||
" \"localStorage\":[\n" +
|
||||
" 'origin':'https://www.example.com',\n" +
|
||||
" 'localStorage':[\n" +
|
||||
" {\n" +
|
||||
" \"name\":\"name1\",\n" +
|
||||
" \"value\":\"value1\"\n" +
|
||||
" 'name':'name1',\n" +
|
||||
" 'value':'value1'\n" +
|
||||
" }]\n" +
|
||||
" }]\n" +
|
||||
"}\n", JsonObject.class);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestBrowserContextStrict extends TestBase {
|
||||
@Override
|
||||
BrowserContext createContext() {
|
||||
return browser.newContext(new Browser.NewContextOptions().setStrictSelectors(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFailPageTextContentInNonStrictMode() {
|
||||
try (BrowserContext context = browser.newContext()) {
|
||||
Page page = context.newPage();
|
||||
page.setContent("<span>span1</span><div><span>target</span></div>");
|
||||
assertEquals("span1", page.textContent("span"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailPageTextContentInStrictMode() {
|
||||
page.setContent("<span>span1</span><div><span>target</span></div>");
|
||||
try {
|
||||
page.textContent("span");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("strict mode violation"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOptOutOfStrictMode() {
|
||||
page.setContent("<span>span1</span><div><span>target</span></div>");
|
||||
assertEquals("span1", page.textContent("span", new Page.TextContentOptions().setStrict(false)));
|
||||
}
|
||||
}
|
||||
@@ -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,29 @@
|
||||
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.*;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestBrowserTypeConnect extends TestBase {
|
||||
private static Process browserServer;
|
||||
private static String wsEndpoint;
|
||||
private Process browserServer;
|
||||
private String wsEndpoint;
|
||||
|
||||
private static class BrowserServer {
|
||||
Process process;
|
||||
@@ -39,13 +48,18 @@ 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 driver = Driver.ensureDriverInstalled(Collections.emptyMap());
|
||||
Path dir = driver.getParent();
|
||||
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
|
||||
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
|
||||
@@ -67,20 +81,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 +124,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 +155,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 +223,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 +235,246 @@ 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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportTracingOverWebSocket(@TempDir Path tempDir) throws IOException {
|
||||
List<BrowserContext> contexts = browser.contexts();
|
||||
assertEquals(1, contexts.size());
|
||||
BrowserContext context = contexts.get(0);
|
||||
|
||||
Page page = context.newPage();
|
||||
context.tracing().start(new Tracing.StartOptions().setName("test")
|
||||
.setScreenshots(true).setSnapshots(true));
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<button>Click</button>");
|
||||
page.click("'Click'");
|
||||
page.close();
|
||||
Path traceFile = tempDir.resolve("trace.zip");
|
||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
|
||||
|
||||
assertTrue(Files.exists(traceFile));
|
||||
assertTrue(Files.size(traceFile) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-specific API")
|
||||
public class TestChromium extends TestBase {
|
||||
@Override
|
||||
void createContextAndPage() {
|
||||
// Do not create anything.
|
||||
}
|
||||
|
||||
private 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportTracingOverCDP(@TempDir Path tempDir) throws IOException {
|
||||
int port = nextPort++;
|
||||
try (Browser browserServer = browserType.launch(createLaunchOptions()
|
||||
.setArgs(asList("--remote-debugging-port=" + port)))) {
|
||||
try (Browser cdpBrowser = browserType.connectOverCDP("http://localhost:" + port)) {
|
||||
List<BrowserContext> contexts = cdpBrowser.contexts();
|
||||
assertEquals(1, contexts.size());
|
||||
BrowserContext context = contexts.get(0);
|
||||
|
||||
Page page = context.newPage();
|
||||
context.tracing().start(new Tracing.StartOptions().setName("test")
|
||||
.setScreenshots(true).setSnapshots(true));
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<button>Click</button>");
|
||||
page.click("'Click'");
|
||||
page.close();
|
||||
Path traceFile = tempDir.resolve("trace.zip");
|
||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
|
||||
|
||||
assertTrue(Files.exists(traceFile));
|
||||
assertTrue(Files.size(traceFile) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
|
||||
import static com.microsoft.playwright.options.KeyboardModifier.ALT;
|
||||
import static com.microsoft.playwright.Utils.copy;
|
||||
@@ -58,6 +59,54 @@ public class TestDownload extends TestBase {
|
||||
writer.write("Hello world");
|
||||
}
|
||||
});
|
||||
|
||||
server.setRoute("/downloadWithDelay", exchange -> {
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
|
||||
exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=file.txt");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
// Chromium requires a large enough payload to trigger the download event soon enough
|
||||
OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody());
|
||||
writer.write(String.join("", Collections.nCopies(4096, "a")));
|
||||
writer.write("foo");
|
||||
writer.flush();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportDownloadWhenNavigationTurnsIntoDownload() throws IOException {
|
||||
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
|
||||
|
||||
Response[] response = new Response[]{null};
|
||||
PlaywrightException[] error = new PlaywrightException[]{null};
|
||||
Download download = page.waitForDownload(() -> {
|
||||
try {
|
||||
response[0] = page.navigate(server.PREFIX + "/download");
|
||||
} catch (PlaywrightException e) {
|
||||
error[0] = e;
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(page, download.page());
|
||||
assertEquals(server.PREFIX + "/download", download.url());
|
||||
Path path = download.path();
|
||||
assertTrue(Files.exists(path));
|
||||
byte[] bytes = readAllBytes(path);
|
||||
assertEquals("Hello world", new String(bytes, UTF_8));
|
||||
if (isChromium()) {
|
||||
assertNotNull(error[0]);
|
||||
assertTrue(error[0].getMessage().contains("net::ERR_ABORTED"));
|
||||
assertEquals("about:blank", page.url());
|
||||
} else if (isWebKit()) {
|
||||
assertNotNull(error[0]);
|
||||
assertTrue(error[0].getMessage().contains("Download is starting"));
|
||||
assertEquals("about:blank", page.url());
|
||||
} else {
|
||||
assertNotNull(response[0]);
|
||||
assertEquals(200, response[0].status());
|
||||
assertEquals(server.PREFIX + "/download", page.url());
|
||||
}
|
||||
page.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -213,7 +262,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();
|
||||
}
|
||||
@@ -354,6 +403,19 @@ public class TestDownload extends TestBase {
|
||||
page.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamShouldSupportZeroSizeRead() throws IOException {
|
||||
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
|
||||
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
|
||||
Download download = page.waitForDownload(() -> page.click("a"));
|
||||
|
||||
InputStream stream = download.createReadStream();
|
||||
byte[] b = new byte[1];
|
||||
int read = stream.read(b, 0, 0);
|
||||
assertEquals(0, read);
|
||||
page.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteDownloadsOnContextDestruction() {
|
||||
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
|
||||
@@ -385,4 +447,30 @@ public class TestDownload extends TestBase {
|
||||
assertFalse(Files.exists(path2));
|
||||
assertFalse(Files.exists(path1.resolve("..")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeAbleToCancelPendingDownloads() {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true))) {
|
||||
page.setContent("<a href='" + server.PREFIX + "/downloadWithDelay'>download</a>");
|
||||
Download download = page.waitForDownload(() -> page.click("a"));
|
||||
download.cancel();
|
||||
String failure = download.failure();
|
||||
assertEquals("canceled", failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFailExplicitlyToCancelADownloadEvenIfThatIsAlreadyFinished() throws IOException {
|
||||
try (Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true))) {
|
||||
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
|
||||
Download download = page.waitForDownload(() -> page.click("a"));
|
||||
|
||||
Path path = download.path();
|
||||
assertTrue(Files.exists(path));
|
||||
byte[] bytes = readAllBytes(path);
|
||||
assertEquals("Hello world", new String(bytes, UTF_8));
|
||||
download.cancel();
|
||||
assertNull(download.failure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,33 @@ public class TestElementHandleConvenience extends TestBase {
|
||||
assertNull(page.getAttribute("#outer", "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void inputValueShouldWork() {
|
||||
page.navigate(server.PREFIX + "/dom.html");
|
||||
|
||||
page.fill("#textarea", "text value");
|
||||
assertEquals("text value", page.inputValue("#textarea"));
|
||||
|
||||
page.fill("#input", "input value");
|
||||
assertEquals("input value", page.inputValue("#input"));
|
||||
ElementHandle handle = page.querySelector("#input");
|
||||
assertEquals("input value", handle.inputValue());
|
||||
|
||||
try {
|
||||
page.inputValue("#inner");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
|
||||
}
|
||||
ElementHandle handle2 = page.querySelector("#inner");
|
||||
try {
|
||||
handle2.inputValue();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Node is not an HTMLInputElement or HTMLTextAreaElement"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerHTMLShouldWork() {
|
||||
page.navigate(server.PREFIX + "/dom.html");
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestElementHandleMisc extends TestBase {
|
||||
@Test
|
||||
void shouldHover() {
|
||||
page.navigate(server.PREFIX + "/input/scrollable.html");
|
||||
ElementHandle button = page.querySelector("#button-6");
|
||||
button.hover();
|
||||
assertEquals("button-6", page.evaluate("document.querySelector('button:hover').id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHoverWhenNodeIsRemoved() {
|
||||
page.navigate(server.PREFIX + "/input/scrollable.html");
|
||||
page.evaluate("() => delete window['Node']");
|
||||
ElementHandle button = page.querySelector("#button-6");
|
||||
button.hover();
|
||||
assertEquals("button-6", page.evaluate("document.querySelector('button:hover').id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFillInput() {
|
||||
page.navigate(server.PREFIX + "/input/textarea.html");
|
||||
ElementHandle handle = page.querySelector("input");
|
||||
handle.fill("some value");
|
||||
assertEquals("some value", page.evaluate("window['result']"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFillInputWhenNodeIsRemoved() {
|
||||
page.navigate(server.PREFIX + "/input/textarea.html");
|
||||
page.evaluate("() => delete window['Node']");
|
||||
ElementHandle handle = page.querySelector("input");
|
||||
handle.fill("some value");
|
||||
assertEquals("some value", page.evaluate("window['result']"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCheckTheBox() {
|
||||
page.setContent("<input id='checkbox' type='checkbox'></input>");
|
||||
ElementHandle input = page.querySelector("input");
|
||||
input.check();
|
||||
assertEquals(true, page.evaluate("checkbox.checked"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUncheckTheBox() {
|
||||
page.setContent("<input id='checkbox' type='checkbox' checked></input>");
|
||||
ElementHandle input = page.querySelector("input");
|
||||
input.uncheck();
|
||||
assertEquals(false, page.evaluate("checkbox.checked"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSelectSingleOption() {
|
||||
page.navigate(server.PREFIX + "/input/select.html");
|
||||
ElementHandle select = page.querySelector("select");
|
||||
select.selectOption("blue");
|
||||
assertEquals(asList("blue"), page.evaluate("window['result'].onInput"));
|
||||
assertEquals(asList("blue"), page.evaluate("window['result'].onChange"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFocusAButton() {
|
||||
page.navigate(server.PREFIX + "/input/button.html");
|
||||
ElementHandle button = page.querySelector("button");
|
||||
assertEquals(false, button.evaluate("button => document.activeElement === button"));
|
||||
button.focus();
|
||||
assertEquals(true, button.evaluate("button => document.activeElement === button"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,11 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledIf;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
@@ -29,6 +32,7 @@ import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.Utils.getOS;
|
||||
import static com.microsoft.playwright.options.LoadState.DOMCONTENTLOADED;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
@@ -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,57 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestLocatorClick extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldWork() {
|
||||
page.navigate(server.PREFIX + "/input/button.html");
|
||||
Locator button = page.locator("button");
|
||||
button.click();
|
||||
assertEquals("Clicked", page.evaluate("() => window['result']"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithNodeRemoved() {
|
||||
page.navigate(server.PREFIX + "/input/button.html");
|
||||
page.evaluate("() => delete window['Node']");
|
||||
Locator button = page.locator("button");
|
||||
button.click();
|
||||
assertEquals("Clicked", page.evaluate("() => window['result']"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDoubleClickTheButton() {
|
||||
page.navigate(server.PREFIX + "/input/button.html");
|
||||
page.evaluate("() => {\n" +
|
||||
" window['double'] = false;\n" +
|
||||
" const button = document.querySelector('button');\n" +
|
||||
" button.addEventListener('dblclick', event => {\n" +
|
||||
" window['double'] = true;\n" +
|
||||
" });\n" +
|
||||
"}");
|
||||
Locator button = page.locator("button");
|
||||
button.dblclick();
|
||||
assertEquals(true, page.evaluate("double"));
|
||||
assertEquals("Clicked", page.evaluate("result"));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user