Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c8706f1eb | |||
| b000a8b6df | |||
| 16cc466622 | |||
| 958813201a | |||
| bc7a59d852 | |||
| a073eb07ae | |||
| 2dbc6194a3 | |||
| 1d69d924cf | |||
| a171c39601 | |||
| 46baa46e36 | |||
| cba51c5e96 | |||
| c17d5d8a9a | |||
| 89894e15d2 | |||
| a012836779 | |||
| 29c0df6443 | |||
| 33ec902eb9 | |||
| 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,41 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Something doesn't work like it should? Tell us!
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Context:**
|
||||
- Playwright Version: [what Playwright version do you use?]
|
||||
- Operating System: [e.g. Windows, Linux or Mac]
|
||||
- Browser: [e.g. All, Chromium, Firefox, WebKit]
|
||||
- Extra: [any specific details about your environment]
|
||||
|
||||
<!-- CLI to auto-capture this info -->
|
||||
<!-- npx envinfo --preset playwright --markdown -->
|
||||
|
||||
**Code Snippet**
|
||||
|
||||
Help us help you! Put down a short code snippet that illustrates your bug and
|
||||
that we can run and debug locally. For example:
|
||||
|
||||
```java
|
||||
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();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
Add any other details about the problem here.
|
||||
@@ -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,11 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Request new features to be added
|
||||
title: "[Feature]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Let us know what functionality you'd like to see in Playwright and what your use case is.
|
||||
Do you think others might benefit from this as well?
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: I have a question
|
||||
about: Feel free to ask us your questions!
|
||||
title: "[Question]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Report regression
|
||||
about: Functionality that used to work and does not any more
|
||||
title: "[REGRESSION]: "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Context:**
|
||||
- GOOD Playwright Version: [what Playwright version worked nicely?]
|
||||
- BAD Playwright Version: [what Playwright version doesn't work any more?]
|
||||
- Operating System: [e.g. Windows, Linux or Mac]
|
||||
- Extra: [any specific details about your environment]
|
||||
|
||||
**Code Snippet**
|
||||
|
||||
Help us help you! Put down a short code snippet that illustrates your bug and
|
||||
that we can run and debug locally. For example:
|
||||
|
||||
```java
|
||||
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();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
Add any other details about the problem here.
|
||||
@@ -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
|
||||
|
||||
+15
-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,11 +28,15 @@ 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
|
||||
mvn test
|
||||
# Executing a single test
|
||||
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
|
||||
# Executing a single test class
|
||||
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
|
||||
```
|
||||
|
||||
### Generating API
|
||||
|
||||
+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 -->96.0.4641.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->92.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.14.1</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.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Rolling Playwright-Java to the latest Playwright driver
|
||||
|
||||
* make sure to have at least Java 8 and Maven 3.6.3
|
||||
* clone playwright for java: http://github.com/microsoft/playwright-java
|
||||
* set new driver version in `scripts/CLI_VERSION`
|
||||
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
|
||||
* commit & send PR with the roll
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.15.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
@@ -44,7 +44,9 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -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.15.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
@@ -40,6 +40,9 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -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.15.0</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -15,7 +15,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.11.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -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.15.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
@@ -50,9 +50,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -60,13 +60,25 @@ 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#navigate Page.navigate()}, {@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;
|
||||
/**
|
||||
@@ -77,6 +89,14 @@ public interface Browser extends AutoCloseable {
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public Map<String, String> extraHTTPHeaders;
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
@@ -114,9 +134,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 +146,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 +162,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 +183,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,30 +200,72 @@ 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;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public NewContextOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When using {@link Page#navigate Page.navigate()}, {@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 NewContextOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
public NewContextOptions setBypassCSP(boolean bypassCSP) {
|
||||
this.bypassCSP = bypassCSP;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
|
||||
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
|
||||
*/
|
||||
public NewContextOptions setColorScheme(ColorScheme colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
|
||||
*/
|
||||
public NewContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
|
||||
this.deviceScaleFactor = deviceScaleFactor;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
|
||||
this.extraHTTPHeaders = extraHTTPHeaders;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public NewContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -190,86 +273,207 @@ public interface Browser extends AutoCloseable {
|
||||
this.geolocation = geolocation;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
*/
|
||||
public NewContextOptions setHasTouch(boolean hasTouch) {
|
||||
this.hasTouch = hasTouch;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public NewContextOptions setHttpCredentials(String username, String password) {
|
||||
return setHttpCredentials(new HttpCredentials(username, password));
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
|
||||
this.httpCredentials = httpCredentials;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
*/
|
||||
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
|
||||
* in Firefox.
|
||||
*/
|
||||
public NewContextOptions setIsMobile(boolean isMobile) {
|
||||
this.isMobile = isMobile;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
|
||||
*/
|
||||
public NewContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
|
||||
this.javaScriptEnabled = javaScriptEnabled;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
|
||||
* request header value as well as number and date formatting rules.
|
||||
*/
|
||||
public NewContextOptions setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to emulate network being offline. Defaults to {@code false}.
|
||||
*/
|
||||
public NewContextOptions setOffline(boolean offline) {
|
||||
this.offline = offline;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
|
||||
* BrowserContext.grantPermissions()} for more details.
|
||||
*/
|
||||
public NewContextOptions setPermissions(List<String> permissions) {
|
||||
this.permissions = permissions;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setProxy(String server) {
|
||||
return setProxy(new Proxy(server));
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
public NewContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
|
||||
this.recordHarOmitContent = recordHarOmitContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setRecordHarPath(Path recordHarPath) {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setRecordVideoDir(Path recordVideoDir) {
|
||||
this.recordVideoDir = recordVideoDir;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public NewContextOptions setRecordVideoSize(int width, int height) {
|
||||
return setRecordVideoSize(new RecordVideoSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public NewContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
|
||||
this.recordVideoSize = recordVideoSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setReducedMotion(ReducedMotion reducedMotion) {
|
||||
this.reducedMotion = reducedMotion;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public NewContextOptions setScreenSize(int width, int height) {
|
||||
return setScreenSize(new ScreenSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public NewContextOptions setScreenSize(ScreenSize screenSize) {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
*/
|
||||
public NewContextOptions setStorageState(String storageState) {
|
||||
this.storageState = storageState;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
|
||||
* state.
|
||||
*/
|
||||
public NewContextOptions setStorageStatePath(Path storageStatePath) {
|
||||
this.storageStatePath = storageStatePath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewContextOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Changes the timezone of the context. See <a
|
||||
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
|
||||
* metaZones.txt</a> for a list of supported timezone IDs.
|
||||
*/
|
||||
public NewContextOptions setTimezoneId(String timezoneId) {
|
||||
this.timezoneId = timezoneId;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specific user agent to use in this context.
|
||||
*/
|
||||
public NewContextOptions setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public NewContextOptions setViewportSize(int width, int height) {
|
||||
return setViewportSize(new ViewportSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public NewContextOptions setViewportSize(ViewportSize viewportSize) {
|
||||
this.viewportSize = Optional.ofNullable(viewportSize);
|
||||
return this;
|
||||
@@ -280,13 +484,25 @@ 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#navigate Page.navigate()}, {@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;
|
||||
/**
|
||||
@@ -297,6 +513,14 @@ public interface Browser extends AutoCloseable {
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public Map<String, String> extraHTTPHeaders;
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
@@ -334,9 +558,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 +570,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 +586,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 +607,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,30 +624,72 @@ 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;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public NewPageOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When using {@link Page#navigate Page.navigate()}, {@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 NewPageOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
public NewPageOptions setBypassCSP(boolean bypassCSP) {
|
||||
this.bypassCSP = bypassCSP;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
|
||||
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
|
||||
*/
|
||||
public NewPageOptions setColorScheme(ColorScheme colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
|
||||
*/
|
||||
public NewPageOptions setDeviceScaleFactor(double deviceScaleFactor) {
|
||||
this.deviceScaleFactor = deviceScaleFactor;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public NewPageOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
|
||||
this.extraHTTPHeaders = extraHTTPHeaders;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public NewPageOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -410,91 +697,248 @@ public interface Browser extends AutoCloseable {
|
||||
this.geolocation = geolocation;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
*/
|
||||
public NewPageOptions setHasTouch(boolean hasTouch) {
|
||||
this.hasTouch = hasTouch;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public NewPageOptions setHttpCredentials(String username, String password) {
|
||||
return setHttpCredentials(new HttpCredentials(username, password));
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public NewPageOptions setHttpCredentials(HttpCredentials httpCredentials) {
|
||||
this.httpCredentials = httpCredentials;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
*/
|
||||
public NewPageOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
|
||||
* in Firefox.
|
||||
*/
|
||||
public NewPageOptions setIsMobile(boolean isMobile) {
|
||||
this.isMobile = isMobile;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
|
||||
*/
|
||||
public NewPageOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
|
||||
this.javaScriptEnabled = javaScriptEnabled;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
|
||||
* request header value as well as number and date formatting rules.
|
||||
*/
|
||||
public NewPageOptions setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to emulate network being offline. Defaults to {@code false}.
|
||||
*/
|
||||
public NewPageOptions setOffline(boolean offline) {
|
||||
this.offline = offline;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
|
||||
* BrowserContext.grantPermissions()} for more details.
|
||||
*/
|
||||
public NewPageOptions setPermissions(List<String> permissions) {
|
||||
this.permissions = permissions;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setProxy(String server) {
|
||||
return setProxy(new Proxy(server));
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
public NewPageOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
|
||||
this.recordHarOmitContent = recordHarOmitContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setRecordHarPath(Path recordHarPath) {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setRecordVideoDir(Path recordVideoDir) {
|
||||
this.recordVideoDir = recordVideoDir;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public NewPageOptions setRecordVideoSize(int width, int height) {
|
||||
return setRecordVideoSize(new RecordVideoSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public NewPageOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
|
||||
this.recordVideoSize = recordVideoSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setReducedMotion(ReducedMotion reducedMotion) {
|
||||
this.reducedMotion = reducedMotion;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public NewPageOptions setScreenSize(int width, int height) {
|
||||
return setScreenSize(new ScreenSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public NewPageOptions setScreenSize(ScreenSize screenSize) {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}.
|
||||
*/
|
||||
public NewPageOptions setStorageState(String storageState) {
|
||||
this.storageState = storageState;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
|
||||
* state.
|
||||
*/
|
||||
public NewPageOptions setStorageStatePath(Path storageStatePath) {
|
||||
this.storageStatePath = storageStatePath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 NewPageOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Changes the timezone of the context. See <a
|
||||
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
|
||||
* metaZones.txt</a> for a list of supported timezone IDs.
|
||||
*/
|
||||
public NewPageOptions setTimezoneId(String timezoneId) {
|
||||
this.timezoneId = timezoneId;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specific user agent to use in this context.
|
||||
*/
|
||||
public NewPageOptions setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public NewPageOptions setViewportSize(int width, int height) {
|
||||
return setViewportSize(new ViewportSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public NewPageOptions setViewportSize(ViewportSize viewportSize) {
|
||||
this.viewportSize = Optional.ofNullable(viewportSize);
|
||||
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;
|
||||
|
||||
/**
|
||||
* specify custom categories to use instead of default.
|
||||
*/
|
||||
public StartTracingOptions setCategories(List<String> categories) {
|
||||
this.categories = categories;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A path to write the trace file to.
|
||||
*/
|
||||
public StartTracingOptions setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* captures screenshots in the trace.
|
||||
*/
|
||||
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 +1007,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
|
||||
@@ -88,6 +137,10 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
public Boolean handle;
|
||||
|
||||
/**
|
||||
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
|
||||
* supported. When passing by value, multiple arguments are supported.
|
||||
*/
|
||||
public ExposeBindingOptions setHandle(boolean handle) {
|
||||
this.handle = handle;
|
||||
return this;
|
||||
@@ -99,11 +152,28 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
public String origin;
|
||||
|
||||
/**
|
||||
* The [origin] to grant permissions to, e.g. "https://example.com".
|
||||
*/
|
||||
public GrantPermissionsOptions setOrigin(String origin) {
|
||||
this.origin = origin;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class RouteOptions {
|
||||
/**
|
||||
* How often a route should be used. By default it will be used every time.
|
||||
*/
|
||||
public Integer times;
|
||||
|
||||
/**
|
||||
* How often a route should be used. By default it will be used every time.
|
||||
*/
|
||||
public RouteOptions setTimes(int times) {
|
||||
this.times = times;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StorageStateOptions {
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
@@ -111,6 +181,10 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
public Path path;
|
||||
|
||||
/**
|
||||
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
*/
|
||||
public StorageStateOptions setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
@@ -127,10 +201,17 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Receives the {@code Page} object and resolves to truthy value when the waiting should resolve.
|
||||
*/
|
||||
public WaitForPageOptions setPredicate(Predicate<Page> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public WaitForPageOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -363,7 +444,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 +458,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 +472,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 +551,11 @@ 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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,20 +573,41 @@ 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);
|
||||
default void route(String url, Consumer<Route> handler) {
|
||||
route(url, handler, null);
|
||||
}
|
||||
/**
|
||||
* 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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,20 +625,39 @@ 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);
|
||||
void route(String url, Consumer<Route> handler, RouteOptions options);
|
||||
/**
|
||||
* 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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,21 +675,190 @@ 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);
|
||||
default void route(Pattern url, Consumer<Route> handler) {
|
||||
route(url, handler, null);
|
||||
}
|
||||
/**
|
||||
* 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> or the same snippet using a regex pattern instead:
|
||||
* <pre>{@code
|
||||
* BrowserContext context = browser.newContext();
|
||||
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> 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. 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, RouteOptions options);
|
||||
/**
|
||||
* 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> or the same snippet using a regex pattern instead:
|
||||
* <pre>{@code
|
||||
* BrowserContext context = browser.newContext();
|
||||
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> 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. 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.
|
||||
*/
|
||||
default void route(Predicate<String> url, Consumer<Route> handler) {
|
||||
route(url, handler, null);
|
||||
}
|
||||
/**
|
||||
* 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> <strong>NOTE:</strong> {@link Page#route Page.route()} will not intercept requests intercepted by Service Worker. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception. Via {@code await context.addInitScript(() => delete window.navigator.serviceWorker);}
|
||||
*
|
||||
* <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());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> or the same snippet using a regex pattern instead:
|
||||
* <pre>{@code
|
||||
* BrowserContext context = browser.newContext();
|
||||
* context.route(Pattern.compile("(\\.png$)|(\\.jpg$)"), route -> route.abort());
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://example.com");
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> 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. 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, RouteOptions options);
|
||||
/**
|
||||
* This setting will change the default maximum navigation time for the following methods and related shortcuts:
|
||||
* <ul>
|
||||
* <li> {@link Page#goBack Page.goBack()}</li>
|
||||
* <li> {@link Page#goForward Page.goForward()}</li>
|
||||
* <li> {@link Page#goto Page.goto()}</li>
|
||||
* <li> {@link Page#navigate Page.navigate()}</li>
|
||||
* <li> {@link Page#reload Page.reload()}</li>
|
||||
* <li> {@link Page#setContent Page.setContent()}</li>
|
||||
* <li> {@link Page#waitForNavigation Page.waitForNavigation()}</li>
|
||||
@@ -625,6 +919,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,21 +57,82 @@ public interface BrowserType {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Additional HTTP headers to be sent with web socket connect request. Optional.
|
||||
*/
|
||||
public ConnectOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
* Defaults to 0.
|
||||
*/
|
||||
public ConnectOptions setSlowMo(double slowMo) {
|
||||
this.slowMo = slowMo;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 ConnectOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Additional HTTP headers to be sent with connect request. Optional.
|
||||
*/
|
||||
public ConnectOverCDPOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
* Defaults to 0.
|
||||
*/
|
||||
public ConnectOverCDPOptions setSlowMo(double slowMo) {
|
||||
this.slowMo = slowMo;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 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}.
|
||||
*/
|
||||
@@ -79,7 +144,8 @@ public interface BrowserType {
|
||||
public Boolean devtools;
|
||||
/**
|
||||
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
|
||||
* deleted when browser is closed.
|
||||
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
|
||||
* is closed.
|
||||
*/
|
||||
public Path downloadsPath;
|
||||
/**
|
||||
@@ -139,74 +205,168 @@ public interface BrowserType {
|
||||
* disable timeout.
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* If specified, traces are saved into this directory.
|
||||
*/
|
||||
public Path tracesDir;
|
||||
|
||||
/**
|
||||
* 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 LaunchOptions setArgs(List<String> args) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
@Deprecated
|
||||
/**
|
||||
* 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 LaunchOptions setChannel(BrowserChannel channel) {
|
||||
this.channel = channel;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchOptions setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enable Chromium sandboxing. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchOptions setChromiumSandbox(boolean chromiumSandbox) {
|
||||
this.chromiumSandbox = chromiumSandbox;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
|
||||
* option will be set {@code false}.
|
||||
*/
|
||||
public LaunchOptions setDevtools(boolean devtools) {
|
||||
this.devtools = devtools;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
|
||||
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
|
||||
* is closed.
|
||||
*/
|
||||
public LaunchOptions setDownloadsPath(Path downloadsPath) {
|
||||
this.downloadsPath = downloadsPath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
|
||||
*/
|
||||
public LaunchOptions setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
|
||||
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
|
||||
* or WebKit, use at your own risk.
|
||||
*/
|
||||
public LaunchOptions setExecutablePath(Path executablePath) {
|
||||
this.executablePath = executablePath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Firefox user preferences. Learn more about the Firefox user preferences at <a
|
||||
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
|
||||
*/
|
||||
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
|
||||
this.firefoxUserPrefs = firefoxUserPrefs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on SIGHUP. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchOptions setHandleSIGHUP(boolean handleSIGHUP) {
|
||||
this.handleSIGHUP = handleSIGHUP;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on Ctrl-C. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchOptions setHandleSIGINT(boolean handleSIGINT) {
|
||||
this.handleSIGINT = handleSIGINT;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on SIGTERM. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchOptions setHandleSIGTERM(boolean handleSIGTERM) {
|
||||
this.handleSIGTERM = handleSIGTERM;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to run browser in headless mode. More details for <a
|
||||
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
|
||||
* {@code devtools} option is {@code true}.
|
||||
*/
|
||||
public LaunchOptions setHeadless(boolean headless) {
|
||||
this.headless = headless;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
|
||||
* use with care. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
|
||||
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
|
||||
* use with care.
|
||||
*/
|
||||
public LaunchOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
|
||||
this.ignoreDefaultArgs = ignoreDefaultArgs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public LaunchOptions setProxy(String server) {
|
||||
return setProxy(new Proxy(server));
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public LaunchOptions setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
*/
|
||||
public LaunchOptions setSlowMo(double slowMo) {
|
||||
this.slowMo = slowMo;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
*/
|
||||
public LaunchOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, traces are saved into this directory.
|
||||
*/
|
||||
public LaunchOptions setTracesDir(Path tracesDir) {
|
||||
this.tracesDir = tracesDir;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class LaunchPersistentContextOptions {
|
||||
/**
|
||||
@@ -218,17 +378,35 @@ public interface BrowserType {
|
||||
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
|
||||
*/
|
||||
public List<String> args;
|
||||
/**
|
||||
* When using {@link Page#navigate Page.navigate()}, {@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;
|
||||
/**
|
||||
@@ -242,7 +420,8 @@ public interface BrowserType {
|
||||
public Boolean devtools;
|
||||
/**
|
||||
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
|
||||
* deleted when browser is closed.
|
||||
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
|
||||
* is closed.
|
||||
*/
|
||||
public Path downloadsPath;
|
||||
/**
|
||||
@@ -251,14 +430,22 @@ 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;
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public Map<String, String> extraHTTPHeaders;
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
/**
|
||||
* Close the browser process on SIGHUP. Defaults to {@code true}.
|
||||
@@ -333,11 +520,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 +536,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,59 +567,148 @@ 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;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setAcceptDownloads(boolean acceptDownloads) {
|
||||
this.acceptDownloads = acceptDownloads;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setArgs(List<String> args) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When using {@link Page#navigate Page.navigate()}, {@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 LaunchPersistentContextOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setBypassCSP(boolean bypassCSP) {
|
||||
this.bypassCSP = bypassCSP;
|
||||
return this;
|
||||
}
|
||||
@Deprecated
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
|
||||
this.channel = channel;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Enable Chromium sandboxing. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setChromiumSandbox(boolean chromiumSandbox) {
|
||||
this.chromiumSandbox = chromiumSandbox;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
|
||||
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Defaults to {@code "light"}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
|
||||
this.deviceScaleFactor = deviceScaleFactor;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
|
||||
* option will be set {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
|
||||
this.devtools = devtools;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
|
||||
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
|
||||
* is closed.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setDownloadsPath(Path downloadsPath) {
|
||||
this.downloadsPath = downloadsPath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify environment variables that will be visible to the browser. Defaults to {@code process.env}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
|
||||
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
|
||||
* or WebKit, use at your own risk.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setExecutablePath(Path executablePath) {
|
||||
this.executablePath = executablePath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
|
||||
this.extraHTTPHeaders = extraHTTPHeaders;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link Page#emulateMedia
|
||||
* Page.emulateMedia()} for more details. Defaults to {@code "none"}.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> It's not supported in WebKit, see <a href="https://bugs.webkit.org/show_bug.cgi?id=225281">here</a> in their issue
|
||||
* tracker.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -422,110 +716,251 @@ public interface BrowserType {
|
||||
this.geolocation = geolocation;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on SIGHUP. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHandleSIGHUP(boolean handleSIGHUP) {
|
||||
this.handleSIGHUP = handleSIGHUP;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on Ctrl-C. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHandleSIGINT(boolean handleSIGINT) {
|
||||
this.handleSIGINT = handleSIGINT;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Close the browser process on SIGTERM. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHandleSIGTERM(boolean handleSIGTERM) {
|
||||
this.handleSIGTERM = handleSIGTERM;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHasTouch(boolean hasTouch) {
|
||||
this.hasTouch = hasTouch;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to run browser in headless mode. More details for <a
|
||||
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
|
||||
* {@code devtools} option is {@code true}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHeadless(boolean headless) {
|
||||
this.headless = headless;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHttpCredentials(String username, String password) {
|
||||
return setHttpCredentials(new HttpCredentials(username, password));
|
||||
}
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
|
||||
this.httpCredentials = httpCredentials;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
|
||||
* use with care. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
|
||||
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
|
||||
* use with care.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
|
||||
this.ignoreDefaultArgs = ignoreDefaultArgs;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
|
||||
* in Firefox.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setIsMobile(boolean isMobile) {
|
||||
this.isMobile = isMobile;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
|
||||
this.javaScriptEnabled = javaScriptEnabled;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
|
||||
* request header value as well as number and date formatting rules.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to emulate network being offline. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setOffline(boolean offline) {
|
||||
this.offline = offline;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
|
||||
* BrowserContext.grantPermissions()} for more details.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setPermissions(List<String> permissions) {
|
||||
this.permissions = permissions;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setProxy(String server) {
|
||||
return setProxy(new Proxy(server));
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordHarOmitContent(boolean recordHarOmitContent) {
|
||||
this.recordHarOmitContent = recordHarOmitContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setRecordHarPath(Path recordHarPath) {
|
||||
this.recordHarPath = recordHarPath;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setRecordVideoDir(Path recordVideoDir) {
|
||||
this.recordVideoDir = recordVideoDir;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordVideoSize(int width, int height) {
|
||||
return setRecordVideoSize(new RecordVideoSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
|
||||
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
|
||||
* be scaled down if necessary to fit the specified size.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
|
||||
this.recordVideoSize = recordVideoSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setReducedMotion(ReducedMotion reducedMotion) {
|
||||
this.reducedMotion = reducedMotion;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setScreenSize(int width, int height) {
|
||||
return setScreenSize(new ScreenSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
|
||||
* is set.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setScreenSize(ScreenSize screenSize) {
|
||||
this.screenSize = screenSize;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setSlowMo(double slowMo) {
|
||||
this.slowMo = slowMo;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 LaunchPersistentContextOptions setStrictSelectors(boolean strictSelectors) {
|
||||
this.strictSelectors = strictSelectors;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Changes the timezone of the context. See <a
|
||||
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
|
||||
* metaZones.txt</a> for a list of supported timezone IDs.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setTimezoneId(String timezoneId) {
|
||||
this.timezoneId = timezoneId;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, traces are saved into this directory.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setTracesDir(Path tracesDir) {
|
||||
this.tracesDir = tracesDir;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specific user agent to use in this context.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setViewportSize(int width, int height) {
|
||||
return setViewportSize(new ViewportSize(width, height));
|
||||
}
|
||||
/**
|
||||
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setViewportSize(ViewportSize viewportSize) {
|
||||
this.viewportSize = Optional.ofNullable(viewportSize);
|
||||
return this;
|
||||
@@ -545,6 +980,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")) {
|
||||
|
||||
@@ -19,14 +19,20 @@ package com.microsoft.playwright;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsole Page.onConsole()} event.
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event.
|
||||
*/
|
||||
public interface ConsoleMessage {
|
||||
/**
|
||||
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsoleMessage Page.onConsoleMessage()}.
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,10 +42,20 @@ public interface FileChooser {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
|
||||
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
|
||||
* inaccessible pages. Defaults to {@code false}.
|
||||
*/
|
||||
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
|
||||
this.noWaitAfter = noWaitAfter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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 SetFilesOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,9 @@ public interface Keyboard {
|
||||
*/
|
||||
public Double delay;
|
||||
|
||||
/**
|
||||
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public PressOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
@@ -71,6 +74,9 @@ public interface Keyboard {
|
||||
*/
|
||||
public Double delay;
|
||||
|
||||
/**
|
||||
* Time to wait between key presses in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public TypeOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
@@ -133,7 +139,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 +176,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 +208,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 +228,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
@@ -49,14 +49,23 @@ public interface Mouse {
|
||||
*/
|
||||
public Double delay;
|
||||
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public ClickOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* defaults to 1. See [UIEvent.detail].
|
||||
*/
|
||||
public ClickOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public ClickOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
@@ -72,10 +81,16 @@ public interface Mouse {
|
||||
*/
|
||||
public Double delay;
|
||||
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public DblclickOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
|
||||
*/
|
||||
public DblclickOptions setDelay(double delay) {
|
||||
this.delay = delay;
|
||||
return this;
|
||||
@@ -91,10 +106,16 @@ public interface Mouse {
|
||||
*/
|
||||
public Integer clickCount;
|
||||
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public DownOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* defaults to 1. See [UIEvent.detail].
|
||||
*/
|
||||
public DownOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
@@ -106,6 +127,9 @@ public interface Mouse {
|
||||
*/
|
||||
public Integer steps;
|
||||
|
||||
/**
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public MoveOptions setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
return this;
|
||||
@@ -121,10 +145,16 @@ public interface Mouse {
|
||||
*/
|
||||
public Integer clickCount;
|
||||
|
||||
/**
|
||||
* Defaults to {@code left}.
|
||||
*/
|
||||
public UpOptions setButton(MouseButton button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* defaults to 1. See [UIEvent.detail].
|
||||
*/
|
||||
public UpOptions setClickCount(int clickCount) {
|
||||
this.clickCount = clickCount;
|
||||
return this;
|
||||
@@ -182,5 +212,15 @@ public interface Mouse {
|
||||
* Dispatches a {@code mouseup} event.
|
||||
*/
|
||||
void up(UpOptions options);
|
||||
/**
|
||||
* Dispatches a {@code wheel} event.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Wheel events may cause scrolling if they are not handled, and this method does not wait for the scrolling to finish
|
||||
* before returning.
|
||||
*
|
||||
* @param deltaX Pixels to scroll horizontally.
|
||||
* @param deltaY Pixels to scroll vertically.
|
||||
*/
|
||||
void wheel(double deltaX, double deltaY);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,12 +40,28 @@ 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;
|
||||
|
||||
/**
|
||||
* Additional environment variables that will be passed to the driver process. By default driver process inherits
|
||||
* environment variables of the Playwright process.
|
||||
*/
|
||||
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 +70,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 +88,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);
|
||||
|
||||
@@ -38,6 +38,10 @@ import java.util.*;
|
||||
* request is issued to a redirected url.
|
||||
*/
|
||||
public interface Request {
|
||||
/**
|
||||
* An object with all the request HTTP headers associated with this request. The header names are lower-cased.
|
||||
*/
|
||||
Map<String, String> allHeaders();
|
||||
/**
|
||||
* The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event.
|
||||
*
|
||||
@@ -54,9 +58,22 @@ public interface Request {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* An object with HTTP headers associated with the request. All header names are lower-case.
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
|
||||
* Request.allHeaders()} instead.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
* An array with all the request HTTP headers associated with this request. Unlike {@link Request#allHeaders
|
||||
* Request.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
|
||||
* the array multiple times.
|
||||
*/
|
||||
List<HttpHeader> headersArray();
|
||||
/**
|
||||
* Returns the value of the header matching the name. The name is case insensitive.
|
||||
*
|
||||
* @param name Name of the header.
|
||||
*/
|
||||
String headerValue(String name);
|
||||
/**
|
||||
* Whether this request is driving frame's navigation.
|
||||
*/
|
||||
@@ -112,6 +129,10 @@ public interface Request {
|
||||
* Returns the matching {@code Response} object, or {@code null} if the response was not received due to error.
|
||||
*/
|
||||
Response response();
|
||||
/**
|
||||
* Returns resource size information for given request.
|
||||
*/
|
||||
Sizes sizes();
|
||||
/**
|
||||
* Returns resource timing information for given request. Most of the timing values become available upon the response,
|
||||
* {@code responseEnd} becomes available when request finishes. Find more information at <a
|
||||
|
||||
@@ -16,18 +16,23 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code Response} class represents responses which are received by page.
|
||||
*/
|
||||
public interface Response {
|
||||
/**
|
||||
* An object with all the response HTTP headers associated with this response.
|
||||
*/
|
||||
Map<String, String> allHeaders();
|
||||
/**
|
||||
* Returns the buffer with response body.
|
||||
*/
|
||||
byte[] body();
|
||||
/**
|
||||
* Waits for this response to finish, returns failure error if request failed.
|
||||
* Waits for this response to finish, returns always {@code null}.
|
||||
*/
|
||||
String finished();
|
||||
/**
|
||||
@@ -35,9 +40,30 @@ public interface Response {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* Returns the object with HTTP headers associated with the response. All header names are lower-case.
|
||||
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
|
||||
* Response.allHeaders()} instead.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
* An array with all the request HTTP headers associated with this response. Unlike {@link Response#allHeaders
|
||||
* Response.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
|
||||
* the array multiple times.
|
||||
*/
|
||||
List<HttpHeader> headersArray();
|
||||
/**
|
||||
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
|
||||
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n} separator is used. If
|
||||
* no headers are found, {@code null} is returned.
|
||||
*
|
||||
* @param name Name of the header.
|
||||
*/
|
||||
String headerValue(String name);
|
||||
/**
|
||||
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case insensitive.
|
||||
*
|
||||
* @param name Name of the header.
|
||||
*/
|
||||
List<String> headerValues(String name);
|
||||
/**
|
||||
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
|
||||
*/
|
||||
@@ -46,6 +72,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).
|
||||
*/
|
||||
|
||||
@@ -43,22 +43,37 @@ public interface Route {
|
||||
*/
|
||||
public String url;
|
||||
|
||||
/**
|
||||
* If set changes the request HTTP headers. Header values will be converted to a string.
|
||||
*/
|
||||
public ResumeOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request method (e.g. GET or POST)
|
||||
*/
|
||||
public ResumeOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public ResumeOptions setPostData(String postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public ResumeOptions setPostData(byte[] postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request URL. New URL must have same protocol as original one.
|
||||
*/
|
||||
public ResumeOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
@@ -91,26 +106,45 @@ public interface Route {
|
||||
*/
|
||||
public Integer status;
|
||||
|
||||
/**
|
||||
* Optional response body as text.
|
||||
*/
|
||||
public FulfillOptions setBody(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional response body as raw bytes.
|
||||
*/
|
||||
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
|
||||
this.bodyBytes = bodyBytes;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set, equals to setting {@code Content-Type} response header.
|
||||
*/
|
||||
public FulfillOptions setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Response headers. Header values will be converted to a string.
|
||||
*/
|
||||
public FulfillOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
|
||||
* is resolved relative to the current working directory.
|
||||
*/
|
||||
public FulfillOptions setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
*/
|
||||
public FulfillOptions setStatus(int status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
@@ -190,7 +224,7 @@ public interface Route {
|
||||
* <p> An example of serving static file:
|
||||
* <pre>{@code
|
||||
* page.route("**\/xhr_endpoint", route -> route.fulfill(
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
|
||||
* }</pre>
|
||||
*/
|
||||
default void fulfill() {
|
||||
@@ -212,7 +246,7 @@ public interface Route {
|
||||
* <p> An example of serving static file:
|
||||
* <pre>{@code
|
||||
* page.route("**\/xhr_endpoint", route -> route.fulfill(
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json")));
|
||||
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
|
||||
* }</pre>
|
||||
*/
|
||||
void fulfill(FulfillOptions options);
|
||||
|
||||
@@ -32,6 +32,11 @@ public interface Selectors {
|
||||
*/
|
||||
public Boolean contentScript;
|
||||
|
||||
/**
|
||||
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
|
||||
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
|
||||
* guaranteed when this engine is used together with other registered engines.
|
||||
*/
|
||||
public RegisterOptions setContentScript(boolean contentScript) {
|
||||
this.contentScript = contentScript;
|
||||
return this;
|
||||
|
||||
@@ -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,182 @@
|
||||
/*
|
||||
* 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 in <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer/">Trace Viewer</a> after Playwright script runs.
|
||||
*
|
||||
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
|
||||
* <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;
|
||||
|
||||
/**
|
||||
* 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 StartOptions setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
|
||||
*/
|
||||
public StartOptions setScreenshots(boolean screenshots) {
|
||||
this.screenshots = screenshots;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to capture DOM snapshot on every action.
|
||||
*/
|
||||
public StartOptions setSnapshots(boolean snapshots) {
|
||||
this.snapshots = snapshots;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StopOptions {
|
||||
/**
|
||||
* Export trace into the file with the given path.
|
||||
*/
|
||||
public Path path;
|
||||
|
||||
/**
|
||||
* Export trace into the file with the given path.
|
||||
*/
|
||||
public StopOptions setPath(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StopChunkOptions {
|
||||
/**
|
||||
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
|
||||
* path.
|
||||
*/
|
||||
public Path path;
|
||||
|
||||
/**
|
||||
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
|
||||
* path.
|
||||
*/
|
||||
public StopChunkOptions 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);
|
||||
/**
|
||||
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
|
||||
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
|
||||
* {@link Tracing#stopChunk Tracing.stopChunk()}.
|
||||
* <pre>{@code
|
||||
* context.tracing().start(new Tracing.StartOptions()
|
||||
* .setScreenshots(true)
|
||||
* .setSnapshots(true));
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://playwright.dev");
|
||||
*
|
||||
* context.tracing().startChunk();
|
||||
* page.click("text=Get Started");
|
||||
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
||||
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||
* .setPath(Paths.get("trace1.zip")));
|
||||
*
|
||||
* context.tracing().startChunk();
|
||||
* page.navigate("http://example.com");
|
||||
* // Save a second trace file with different actions.
|
||||
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||
* .setPath(Paths.get("trace2.zip")));
|
||||
* }</pre>
|
||||
*/
|
||||
void startChunk();
|
||||
/**
|
||||
* Stop tracing.
|
||||
*/
|
||||
default void stop() {
|
||||
stop(null);
|
||||
}
|
||||
/**
|
||||
* Stop tracing.
|
||||
*/
|
||||
void stop(StopOptions options);
|
||||
/**
|
||||
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
|
||||
*/
|
||||
default void stopChunk() {
|
||||
stopChunk(null);
|
||||
}
|
||||
/**
|
||||
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
|
||||
*/
|
||||
void stopChunk(StopChunkOptions 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);
|
||||
/**
|
||||
@@ -72,10 +72,17 @@ public interface WebSocket {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
|
||||
*/
|
||||
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public WaitForFrameReceivedOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -92,10 +99,17 @@ public interface WebSocket {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
|
||||
*/
|
||||
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
|
||||
this.predicate = predicate;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public WaitForFrameSentOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
|
||||
@@ -52,6 +52,10 @@ public interface Worker {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
|
||||
*/
|
||||
public WaitForCloseOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -65,7 +69,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 +86,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,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
Path recordHarPath;
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
PAGE,
|
||||
REQUEST,
|
||||
REQUESTFAILED,
|
||||
REQUESTFINISHED,
|
||||
RESPONSE,
|
||||
}
|
||||
|
||||
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -60,6 +74,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,9 +105,49 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
listeners.remove(EventType.PAGE, handler);
|
||||
}
|
||||
|
||||
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
|
||||
@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, Predicate<T> predicate, Double timeout) {
|
||||
List<Waitable<T>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType));
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
|
||||
waitables.add(new WaitableContextClose<>());
|
||||
waitables.add(timeoutSettings.createWaitable(timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
@@ -92,10 +155,14 @@ 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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.PAGE, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,7 +172,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() {
|
||||
@@ -114,6 +181,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
isClosedOrClosing = true;
|
||||
try {
|
||||
if (recordHarPath != null) {
|
||||
JsonObject json = sendMessage("harExport").getAsJsonObject();
|
||||
ArtifactImpl artifact = 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 (browser() != null && browser().isRemote) {
|
||||
artifact.isRemote = true;
|
||||
}
|
||||
artifact.saveAs(recordHarPath);
|
||||
artifact.delete();
|
||||
}
|
||||
|
||||
sendMessage("close");
|
||||
} catch (PlaywrightException e) {
|
||||
if (!isSafeCloseError(e)) {
|
||||
@@ -177,7 +256,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 +304,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));
|
||||
@@ -251,23 +330,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(String url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(this.baseUrl, url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Pattern url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Predicate<String> url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
|
||||
withLogging("BrowserContext.route", () -> {
|
||||
routes.add(matcher, handler);
|
||||
routes.add(matcher, handler, options == null ? null : options.times);
|
||||
if (routes.size() == 1) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", true);
|
||||
@@ -344,9 +423,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 +479,60 @@ 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);
|
||||
request.didFailOrFinish = true;
|
||||
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);
|
||||
request.didFailOrFinish = true;
|
||||
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) {
|
||||
@@ -167,12 +165,13 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
params.addProperty("noDefaultViewport", true);
|
||||
}
|
||||
}
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
JsonElement result = sendMessage("newContext", params);
|
||||
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
context.videosDir = options.recordVideoDir;
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -182,6 +181,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 = connection.initializePlaywright();
|
||||
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,33 @@ 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("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();
|
||||
}
|
||||
@@ -141,12 +182,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
params.addProperty("noDefaultViewport", true);
|
||||
}
|
||||
}
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
|
||||
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
|
||||
if (options.recordVideoDir != null) {
|
||||
context.videosDir = options.recordVideoDir;
|
||||
context.videosDir = options.recordVideoDir;
|
||||
if (options.baseURL != null) {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -19,16 +19,12 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -36,6 +32,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,10 +64,23 @@ 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) {
|
||||
super(connection, "", "");
|
||||
super(connection, "Root", "");
|
||||
}
|
||||
|
||||
Playwright initialize() {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
|
||||
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +98,12 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
String previous = apiName;
|
||||
apiName = name;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
transport.close();
|
||||
}
|
||||
@@ -148,20 +164,24 @@ 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;
|
||||
}
|
||||
|
||||
public ChannelOwner waitForObjectWithKnownName(String guid) {
|
||||
while (!objects.containsKey(guid)) {
|
||||
processOneMessage();
|
||||
}
|
||||
return objects.get(guid);
|
||||
public PlaywrightImpl initializePlaywright() {
|
||||
return (PlaywrightImpl) this.root.initialize();
|
||||
}
|
||||
|
||||
public <T> T getExistingObject(String guid) {
|
||||
@@ -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,15 +299,16 @@ 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;
|
||||
case "ElementHandle":
|
||||
result = new ElementHandleImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "FetchRequest":
|
||||
// Create fake object as this API is experimental an only exposed in Node.js.
|
||||
result = new ChannelOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Frame":
|
||||
result = new FrameImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
@@ -297,9 +324,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
|
||||
@@ -209,10 +210,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 +237,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", () -> {
|
||||
@@ -412,6 +432,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked, SetCheckedOptions options) {
|
||||
if (checked) {
|
||||
check(convertViaJson(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheck(convertViaJson(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path files, SetInputFilesOptions options) {
|
||||
setInputFiles(new Path[]{files}, options);
|
||||
|
||||
@@ -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();
|
||||
@@ -599,6 +643,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return parseStringList(json.getAsJsonArray("values"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
|
||||
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options));
|
||||
}
|
||||
|
||||
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
|
||||
if (checked) {
|
||||
checkImpl(selector, convertViaJson(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheckImpl(selector, convertViaJson(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Frame.setContent", () -> setContentImpl(html, options));
|
||||
@@ -737,7 +794,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 +916,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 +932,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 +976,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,406 @@
|
||||
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 setChecked(boolean checked, SetCheckedOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetCheckedOptions();
|
||||
}
|
||||
frame.setChecked(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.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(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,16 @@ class MouseImpl implements Mouse {
|
||||
page.withLogging("Mouse.up", () -> upImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wheel(double deltaX, double deltaY) {
|
||||
page.withLogging("Mouse.wheel", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("deltaX", deltaX);
|
||||
params.addProperty("deltaY", deltaY);
|
||||
page.sendMessage("mouseWheel", params);
|
||||
});
|
||||
}
|
||||
|
||||
private void upImpl(UpOptions options) {
|
||||
if (options == null) {
|
||||
options = new UpOptions();
|
||||
|
||||
@@ -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
|
||||
@@ -117,12 +125,15 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
if (listeners.hasListeners(EventType.DIALOG)) {
|
||||
listeners.notify(EventType.DIALOG, dialog);
|
||||
} else {
|
||||
dialog.dismiss();
|
||||
if ("beforeunload".equals(dialog.type())) {
|
||||
try {
|
||||
dialog.accept();
|
||||
} catch (PlaywrightException e) {
|
||||
}
|
||||
} 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 +149,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 +177,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 +205,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 +225,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,67 +445,95 @@ 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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.CLOSE, code, null, options.timeout);
|
||||
}
|
||||
|
||||
@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));
|
||||
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.DOWNLOAD, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.DOWNLOAD, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.FILECHOOSER, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.FILECHOOSER, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.POPUP, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.POPUP, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.WEBSOCKET, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.WEBSOCKET, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
return waitForEventWithTimeout(EventType.WORKER, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
|
||||
List<Waitable<T>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
|
||||
waitables.add(createWaitForCloseHelper());
|
||||
waitables.add(createWaitableTimeout(timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -527,8 +555,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 +566,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 +725,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 +804,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 +822,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 +874,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 +890,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
|
||||
@@ -906,23 +950,23 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(String url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(browserContext.baseUrl, url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Pattern url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Predicate<String> url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
|
||||
withLogging("Page.route", () -> {
|
||||
routes.add(matcher, handler);
|
||||
routes.add(matcher, handler, options == null ? null : options.times);
|
||||
if (routes.size() == 1) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", true);
|
||||
@@ -1005,6 +1049,12 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
|
||||
withLogging("Page.setChecked",
|
||||
() -> mainFrame.setCheckedImpl(selector, checked, convertViaJson(options, Frame.SetCheckedOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Page.setContent",
|
||||
@@ -1115,7 +1165,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 +1194,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 +1234,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 +1304,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 +1314,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) {
|
||||
@@ -1270,17 +1325,24 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
if (options == null) {
|
||||
options = new WaitForRequestOptions();
|
||||
}
|
||||
List<Waitable<Request>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST,
|
||||
request -> predicate == null || predicate.test(request)));
|
||||
waitables.add(createWaitForCloseHelper());
|
||||
waitables.add(createWaitableTimeout(options.timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
return waitForEventWithTimeout(EventType.REQUEST, code, predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.REQUESTFINISHED, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -1301,12 +1363,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
if (options == null) {
|
||||
options = new WaitForResponseOptions();
|
||||
}
|
||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE,
|
||||
response -> predicate == null || predicate.test(response)));
|
||||
waitables.add(createWaitForCloseHelper());
|
||||
waitables.add(createWaitableTimeout(options.timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
return waitForEventWithTimeout(EventType.RESPONSE, code, predicate, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1320,6 +1377,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");
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
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
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class RawHeaders {
|
||||
private final List<HttpHeader> headersArray;
|
||||
private final Map<String, List<String>> headersMap = new LinkedHashMap<>();
|
||||
|
||||
RawHeaders(List<HttpHeader> headers) {
|
||||
headersArray = headers;
|
||||
for (HttpHeader h: headers) {
|
||||
String name = h.name.toLowerCase();
|
||||
List<String> values = headersMap.get(name);
|
||||
if (values == null) {
|
||||
values = new ArrayList<>();
|
||||
headersMap.put(name, values);
|
||||
}
|
||||
values.add(h.value);
|
||||
}
|
||||
}
|
||||
|
||||
String get(String name) {
|
||||
List<String> values = getAll(name);
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
return String.join("set-cookie".equals(name.toLowerCase()) ? "\n" : ", ", values);
|
||||
}
|
||||
|
||||
List<String> getAll(String name) {
|
||||
return headersMap.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
Map<String, String> headers() {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
for (String name: headersMap.keySet()) {
|
||||
result.put(name, get(name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<HttpHeader> headersArray() {
|
||||
return headersArray;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,25 +16,32 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Response;
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
import com.microsoft.playwright.options.Sizes;
|
||||
import com.microsoft.playwright.options.Timing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.toHeadersMap;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class RequestImpl extends ChannelOwner implements Request {
|
||||
private final byte[] postData;
|
||||
private RequestImpl redirectedFrom;
|
||||
private RequestImpl redirectedTo;
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
private final RawHeaders headers;
|
||||
private RawHeaders rawHeaders;
|
||||
String failure;
|
||||
Timing timing;
|
||||
boolean didFailOrFinish;
|
||||
|
||||
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -43,10 +50,7 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
|
||||
redirectedFrom.redirectedTo = this;
|
||||
}
|
||||
for (JsonElement e : initializer.getAsJsonArray("headers")) {
|
||||
JsonObject item = e.getAsJsonObject();
|
||||
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
|
||||
}
|
||||
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
|
||||
if (initializer.has("postData")) {
|
||||
postData = Base64.getDecoder().decode(initializer.get("postData").getAsString());
|
||||
} else {
|
||||
@@ -54,19 +58,34 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> allHeaders() {
|
||||
return withLogging("Request.allHeaders", () -> getRawHeaders().headers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String failure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame frame() {
|
||||
public FrameImpl frame() {
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return headers;
|
||||
return headers.headers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpHeader> headersArray() {
|
||||
return withLogging("Request.headersArray", () -> getRawHeaders().headersArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headerValue(String name) {
|
||||
return withLogging("Request.headerValue", () -> getRawHeaders().get(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +127,7 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response response() {
|
||||
public ResponseImpl response() {
|
||||
return withLogging("Request.response", () -> {
|
||||
JsonObject result = sendMessage("response").getAsJsonObject();
|
||||
if (!result.has("response")) {
|
||||
@@ -118,6 +137,18 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sizes sizes() {
|
||||
return withLogging("Request.sizes", () -> {
|
||||
ResponseImpl response = response();
|
||||
if (response == null) {
|
||||
throw new PlaywrightException("Unable to fetch sizes for failed request");
|
||||
}
|
||||
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
|
||||
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timing timing() {
|
||||
return timing;
|
||||
@@ -132,4 +163,22 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
return redirectedTo != null ? redirectedTo.finalRequest() : this;
|
||||
}
|
||||
|
||||
private RawHeaders getRawHeaders() {
|
||||
if (rawHeaders != null) {
|
||||
return rawHeaders;
|
||||
}
|
||||
ResponseImpl response = response();
|
||||
// there is no response, so should we return the headers we have now?
|
||||
if (response == null) {
|
||||
return headers;
|
||||
}
|
||||
JsonArray rawHeadersJson = response.withLogging("Request.allHeaders", () -> {
|
||||
JsonObject result = response.sendMessage("rawRequestHeaders").getAsJsonObject();
|
||||
return result.getAsJsonArray("headers");
|
||||
});
|
||||
|
||||
// The field may have been initialized in a nested call but it is ok.
|
||||
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
|
||||
return rawHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,37 +16,38 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
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.HttpHeader;
|
||||
import com.microsoft.playwright.options.SecurityDetails;
|
||||
import com.microsoft.playwright.options.ServerAddr;
|
||||
import com.microsoft.playwright.options.Timing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class ResponseImpl extends ChannelOwner implements Response {
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
private final RawHeaders headers;
|
||||
private RawHeaders rawHeaders;
|
||||
private final RequestImpl request;
|
||||
|
||||
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
|
||||
for (JsonElement e : initializer.getAsJsonArray("headers")) {
|
||||
JsonObject item = e.getAsJsonObject();
|
||||
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
|
||||
}
|
||||
|
||||
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
|
||||
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
|
||||
request.headers.clear();
|
||||
for (JsonElement e : initializer.getAsJsonArray("requestHeaders")) {
|
||||
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
|
||||
public Map<String, String> allHeaders() {
|
||||
return withLogging("Response.allHeaders", () -> getRawHeaders().headers());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,13 +60,22 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
|
||||
@Override
|
||||
public String finished() {
|
||||
return withLogging("Response.finished", () -> {
|
||||
JsonObject json = sendMessage("finished").getAsJsonObject();
|
||||
if (json.has("error")) {
|
||||
return json.get("error").getAsString();
|
||||
List<Waitable<String>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitableNever<String>() {
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return request.didFailOrFinish;
|
||||
}
|
||||
@Override
|
||||
public String get() {
|
||||
return request.failure();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
PageImpl page = request.frame().page;
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableTimeout(null));
|
||||
runUntil(() -> {}, new WaitableRace<>(waitables));
|
||||
return request.failure();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +85,22 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return headers;
|
||||
return headers.headers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpHeader> headersArray() {
|
||||
return withLogging("Response.headersArray", () -> getRawHeaders().headersArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headerValue(String name) {
|
||||
return getRawHeaders().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> headerValues(String name) {
|
||||
return getRawHeaders().getAll(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +113,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();
|
||||
@@ -107,4 +154,12 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
public String url() {
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
private RawHeaders getRawHeaders() {
|
||||
if (rawHeaders == null) {
|
||||
JsonObject json = sendMessage("rawResponseHeaders").getAsJsonObject();
|
||||
rawHeaders = new RawHeaders(asList(gson().fromJson(json.getAsJsonArray("headers"), HttpHeader[].class)));
|
||||
}
|
||||
return rawHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,31 @@ class Router {
|
||||
private static class RouteInfo {
|
||||
final UrlMatcher matcher;
|
||||
final Consumer<Route> handler;
|
||||
Integer times;
|
||||
|
||||
RouteInfo(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
RouteInfo(UrlMatcher matcher, Consumer<Route> handler, Integer times) {
|
||||
this.matcher = matcher;
|
||||
this.handler = handler;
|
||||
this.times = times;
|
||||
}
|
||||
|
||||
boolean handle(Route route) {
|
||||
if (times != null && times <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!matcher.test(route.request().url())) {
|
||||
return false;
|
||||
}
|
||||
if (times != null) {
|
||||
--times;
|
||||
}
|
||||
handler.accept(route);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void add(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
routes.add(new RouteInfo(matcher, handler));
|
||||
void add(UrlMatcher matcher, Consumer<Route> handler, Integer times) {
|
||||
routes.add(0, new RouteInfo(matcher, handler, times));
|
||||
}
|
||||
|
||||
void remove(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
@@ -52,8 +68,7 @@ class Router {
|
||||
|
||||
boolean handle(Route route) {
|
||||
for (RouteInfo info : routes) {
|
||||
if (info.matcher.test(route.request().url())) {
|
||||
info.handler.accept(route);
|
||||
if (info.handle(route)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +39,11 @@ class Serialization {
|
||||
if (gson == null) {
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
|
||||
.registerTypeAdapter(Media.class, new MediaSerializer())
|
||||
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
|
||||
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
|
||||
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
|
||||
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
|
||||
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
@@ -48,8 +52,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;
|
||||
}
|
||||
@@ -252,6 +257,8 @@ class Serialization {
|
||||
private static boolean isSupported(Type type) {
|
||||
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
|
||||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
|
||||
}
|
||||
|
||||
@@ -274,6 +281,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,10 +301,16 @@ class Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
private static class MediaSerializer implements JsonSerializer<Media> {
|
||||
private static class BrowserChannelSerializer implements JsonSerializer<BrowserChannel> {
|
||||
@Override
|
||||
public JsonElement serialize(Media src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toString().toLowerCase());
|
||||
public JsonElement serialize(BrowserChannel src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-'));
|
||||
}
|
||||
}
|
||||
private static class ToLowerCaseAndDashSerializer<E extends Enum<E>> implements JsonSerializer<E> {
|
||||
@Override
|
||||
public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.toString().toLowerCase().replace('_', '-'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,37 +354,5 @@ class Serialization {
|
||||
return SameSiteAttribute.valueOf(value.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ColorSchemeAdapter extends TypeAdapter<ColorScheme> {
|
||||
@Override
|
||||
public void write(JsonWriter out, ColorScheme value) throws IOException {
|
||||
String stringValue;
|
||||
switch (value) {
|
||||
case DARK:
|
||||
stringValue = "dark";
|
||||
break;
|
||||
case LIGHT:
|
||||
stringValue = "light";
|
||||
break;
|
||||
case NO_PREFERENCE:
|
||||
stringValue = "no-preference";
|
||||
break;
|
||||
default:
|
||||
throw new PlaywrightException("Unexpected value: " + value);
|
||||
}
|
||||
out.value(stringValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorScheme read(JsonReader in) throws IOException {
|
||||
String value = in.nextString();
|
||||
switch (value) {
|
||||
case "dark": return ColorScheme.DARK;
|
||||
case "light": return ColorScheme.LIGHT;
|
||||
case "no-preference": return ColorScheme.NO_PREFERENCE;
|
||||
default: throw new PlaywrightException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,86 @@
|
||||
/*
|
||||
* 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 stopChunkImpl(Path path) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("save", path != null);
|
||||
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
|
||||
if (!json.has("artifact")) {
|
||||
return;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startChunk() {
|
||||
context.withLogging("Tracing.startChunk", () -> {
|
||||
context.sendMessage("tracingStartChunk");
|
||||
});
|
||||
}
|
||||
|
||||
private void startImpl(StartOptions options) {
|
||||
if (options == null) {
|
||||
options = new StartOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
context.sendMessage("tracingStart", params);
|
||||
context.sendMessage("tracingStartChunk");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(StopOptions options) {
|
||||
context.withLogging("Tracing.stop", () -> {
|
||||
stopChunkImpl(options == null ? null : options.path);
|
||||
context.sendMessage("tracingStop");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopChunk(StopChunkOptions options) {
|
||||
context.withLogging("Tracing.stopChunk", () -> {
|
||||
stopChunkImpl(options == null ? null : options.path);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,41 @@
|
||||
|
||||
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 com.microsoft.playwright.options.HttpHeader;
|
||||
|
||||
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 +180,20 @@ 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();
|
||||
}
|
||||
|
||||
static Map<String, String> toHeadersMap(List<HttpHeader> headers) {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
for (HttpHeader header: headers) {
|
||||
map.put(header.name.toLowerCase(), header.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 @@ 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, predicate));
|
||||
waitables.add(new WaitableWebSocketClose<>());
|
||||
waitables.add(new WaitableWebSocketError<>());
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
@@ -168,14 +177,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
|
||||
}
|
||||
@@ -52,30 +52,51 @@ public class Cookie {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
/**
|
||||
* either url or domain / path are required. Optional.
|
||||
*/
|
||||
public Cookie setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* either url or domain / path are required Optional.
|
||||
*/
|
||||
public Cookie setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* either url or domain / path are required Optional.
|
||||
*/
|
||||
public Cookie setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Unix time in seconds. Optional.
|
||||
*/
|
||||
public Cookie setExpires(double expires) {
|
||||
this.expires = expires;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional.
|
||||
*/
|
||||
public Cookie setHttpOnly(boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional.
|
||||
*/
|
||||
public Cookie setSecure(boolean secure) {
|
||||
this.secure = secure;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional.
|
||||
*/
|
||||
public Cookie setSameSite(SameSiteAttribute sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
return this;
|
||||
|
||||
@@ -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 ForcedColors {
|
||||
ACTIVE,
|
||||
NONE
|
||||
}
|
||||
@@ -34,6 +34,9 @@ public class Geolocation {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
/**
|
||||
* Non-negative accuracy value. Defaults to {@code 0}.
|
||||
*/
|
||||
public Geolocation setAccuracy(double accuracy) {
|
||||
this.accuracy = accuracy;
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 HttpHeader {
|
||||
/**
|
||||
* Name of the header.
|
||||
*/
|
||||
public String name;
|
||||
/**
|
||||
* Value of the header.
|
||||
*/
|
||||
public String value;
|
||||
|
||||
}
|
||||
@@ -34,18 +34,30 @@ public class Margin {
|
||||
*/
|
||||
public String left;
|
||||
|
||||
/**
|
||||
* Top margin, accepts values labeled with units. Defaults to {@code 0}.
|
||||
*/
|
||||
public Margin setTop(String top) {
|
||||
this.top = top;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Right margin, accepts values labeled with units. Defaults to {@code 0}.
|
||||
*/
|
||||
public Margin setRight(String right) {
|
||||
this.right = right;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Bottom margin, accepts values labeled with units. Defaults to {@code 0}.
|
||||
*/
|
||||
public Margin setBottom(String bottom) {
|
||||
this.bottom = bottom;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Left margin, accepts values labeled with units. Defaults to {@code 0}.
|
||||
*/
|
||||
public Margin setLeft(String left) {
|
||||
this.left = left;
|
||||
return this;
|
||||
|
||||
@@ -38,14 +38,23 @@ public class Proxy {
|
||||
public Proxy(String server) {
|
||||
this.server = server;
|
||||
}
|
||||
/**
|
||||
* Optional coma-separated domains to bypass proxy, for example {@code ".com, chromium.org, .domain.com"}.
|
||||
*/
|
||||
public Proxy setBypass(String bypass) {
|
||||
this.bypass = bypass;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional username to use if HTTP proxy requires authentication.
|
||||
*/
|
||||
public Proxy setUsername(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Optional password to use if HTTP proxy requires authentication.
|
||||
*/
|
||||
public Proxy setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum 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;
|
||||
|
||||
}
|
||||
@@ -30,14 +30,23 @@ public class SelectOption {
|
||||
*/
|
||||
public Integer index;
|
||||
|
||||
/**
|
||||
* Matches by {@code option.value}. Optional.
|
||||
*/
|
||||
public SelectOption setValue(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches by {@code option.label}. Optional.
|
||||
*/
|
||||
public SelectOption setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches by the index. Optional.
|
||||
*/
|
||||
public SelectOption setIndex(int index) {
|
||||
this.index = index;
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class ServerAddr {
|
||||
/**
|
||||
* IPv4 or IPV6 address of the server.
|
||||
*/
|
||||
public String ipAddress;
|
||||
public int port;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class Sizes {
|
||||
/**
|
||||
* Size of the request body (POST data payload) in bytes. Set to 0 if there was no body.
|
||||
*/
|
||||
public int requestBodySize;
|
||||
/**
|
||||
* Total number of bytes from the start of the HTTP request message until (and including) the double CRLF before the body.
|
||||
*/
|
||||
public int requestHeadersSize;
|
||||
/**
|
||||
* Size of the received response body (encoded) in bytes.
|
||||
*/
|
||||
public int responseBodySize;
|
||||
/**
|
||||
* Total number of bytes from the start of the HTTP response message until (and including) the double CRLF before the body.
|
||||
*/
|
||||
public int responseHeadersSize;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TestBeforeunload extends TestBase {
|
||||
@Test
|
||||
void shouldBeAbleToNavigateAwayFromPageWithBeforeunload() {
|
||||
page.navigate(server.PREFIX + "/beforeunload.html");
|
||||
// We have to interact with a page so that "beforeunload" handlers
|
||||
// fire.
|
||||
page.click("body");
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -116,10 +115,9 @@ public class TestBrowserContextBasic extends TestBase {
|
||||
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: supported null viewport option")
|
||||
void shouldNotAllowDeviceScaleFactorWithNullViewport() {
|
||||
try {
|
||||
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0));
|
||||
browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0).setViewportSize(null));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\""));
|
||||
@@ -127,10 +125,9 @@ public class TestBrowserContextBasic extends TestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: supported null viewport option")
|
||||
void shouldNotAllowIsMobileWithNullViewport() {
|
||||
try {
|
||||
browser.newContext(new Browser.NewContextOptions().setIsMobile(true));
|
||||
browser.newContext(new Browser.NewContextOptions().setIsMobile(true).setViewportSize(null));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\""));
|
||||
@@ -170,6 +167,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user