Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 50508ed2fa | |||
| e863a78755 | |||
| b1c5ec2083 | |||
| eca3491203 | |||
| 558df1fc60 | |||
| ba5bd2c9ac | |||
| 4be749f045 | |||
| f515d9f318 | |||
| 2f706012a7 | |||
| 853b5062e7 | |||
| 2d0d941e18 | |||
| 49a54d7ee4 | |||
| 44a85c1dc3 | |||
| 5d7ee12f4a | |||
| a0416459e1 | |||
| ddffc45e84 | |||
| e85258908e | |||
| c61d1da352 | |||
| a60b0a9b78 | |||
| 38bde7ad25 | |||
| a8e41b1ede | |||
| 45b141811b | |||
| bd6ed7bc88 | |||
| d0e7ab1e58 | |||
| 7ff7ee188b | |||
| d291a64e11 | |||
| 9f2b482084 | |||
| b7319c629d | |||
| 38c5dc28a4 | |||
| 1b9f7732fe | |||
| 1a4dec86cd | |||
| c802c87e52 | |||
| ab81b542b8 | |||
| 07e7cb85c7 | |||
| 7af5405d38 | |||
| 50ba2dacbb | |||
| 61f5e4dfdd | |||
| 84344c9ff9 | |||
| c0fd575fac | |||
| 81724c7c94 | |||
| 3eb88931bf | |||
| 948dd1515a | |||
| dd57d5248d | |||
| 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:
|
||||
- main
|
||||
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:
|
||||
- main
|
||||
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,15 +1,15 @@
|
||||
name: Test
|
||||
name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- 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:
|
||||
@@ -36,6 +36,56 @@ jobs:
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
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 --fail-at-end
|
||||
env:
|
||||
BROWSER: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
|
||||
@@ -2,17 +2,15 @@ name: Test CLI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Test Docker
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/test_docker.yml'
|
||||
- 'Dockerfile*'
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/test_docker.yml
|
||||
- Dockerfile.*
|
||||
- scripts/CLI_VERSION
|
||||
- '**/pom.xml'
|
||||
branches:
|
||||
- main
|
||||
- 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
|
||||
@@ -2,14 +2,14 @@ name: Verify API
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
paths:
|
||||
- 'scripts/*'
|
||||
- 'api-generator/*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
paths:
|
||||
- 'scripts/**'
|
||||
@@ -17,11 +17,10 @@ on:
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
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 +31,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 -->98.0.4695.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->94.0.1<!-- 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.16.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### Is Playwright thread-safe?
|
||||
|
||||
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create new many Playwright instances each on its own thread.
|
||||
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create multiple Playwright instances each on its own thread.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -173,14 +173,14 @@ 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
|
||||
|
||||
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
|
||||
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
|
||||
|
||||
## Is Playwright for Java ready?
|
||||
|
||||
Yes, Playwright for Java is ready. We are still not at the version v1.0, so breaking API changes could potentially happen. But a) this is unlikely and b) we will only do that if we know it improves your experience with the new library. We'd like to collect your feedback before we freeze the API for v1.0.
|
||||
|
||||
> Note: We don't yet support some of the edge-cases of the vendor-specific APIs such as collecting Chromium trace, coverage report, etc.
|
||||
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.
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# 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
|
||||
|
||||
# Updating Version
|
||||
|
||||
```bash
|
||||
./scripts/set_maven_version.sh 1.15.0
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.17.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>assertions</artifactId>
|
||||
<name>Playwright - Assertions</name>
|
||||
<description>
|
||||
This module provides Playwright assertions that will wait until the expected condition is met.
|
||||
</description>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<subpackages>com.microsoft.playwright.assertions</subpackages>
|
||||
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.opentest4j</groupId>
|
||||
<artifactId>opentest4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.assertions;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import com.microsoft.playwright.Page;
|
||||
|
||||
/**
|
||||
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
|
||||
* tests. A new instance of {@code LocatorAssertions} is created by calling {@link PlaywrightAssertions#assertThat
|
||||
* PlaywrightAssertions.assertThat()}:
|
||||
* <pre>{@code
|
||||
* ...
|
||||
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
*
|
||||
* public class TestPage {
|
||||
* ...
|
||||
* @Test
|
||||
* void navigatesToLoginPage() {
|
||||
* ...
|
||||
* page.click("#login");
|
||||
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public interface PageAssertions {
|
||||
class HasTitleOptions {
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public HasTitleOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class HasURLOptions {
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for.
|
||||
*/
|
||||
public HasURLOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Ensures the page has the given title.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasTitle("Playwright");
|
||||
* }</pre>
|
||||
*
|
||||
* @param titleOrRegExp Expected title or RegExp.
|
||||
*/
|
||||
default void hasTitle(String titleOrRegExp) {
|
||||
hasTitle(titleOrRegExp, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the page has the given title.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasTitle("Playwright");
|
||||
* }</pre>
|
||||
*
|
||||
* @param titleOrRegExp Expected title or RegExp.
|
||||
*/
|
||||
void hasTitle(String titleOrRegExp, HasTitleOptions options);
|
||||
/**
|
||||
* Ensures the page has the given title.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasTitle("Playwright");
|
||||
* }</pre>
|
||||
*
|
||||
* @param titleOrRegExp Expected title or RegExp.
|
||||
*/
|
||||
default void hasTitle(Pattern titleOrRegExp) {
|
||||
hasTitle(titleOrRegExp, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the page has the given title.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasTitle("Playwright");
|
||||
* }</pre>
|
||||
*
|
||||
* @param titleOrRegExp Expected title or RegExp.
|
||||
*/
|
||||
void hasTitle(Pattern titleOrRegExp, HasTitleOptions options);
|
||||
/**
|
||||
* Ensures the page is navigated to the given URL.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
default void hasURL(String urlOrRegExp) {
|
||||
hasURL(urlOrRegExp, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the page is navigated to the given URL.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
void hasURL(String urlOrRegExp, HasURLOptions options);
|
||||
/**
|
||||
* Ensures the page is navigated to the given URL.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
default void hasURL(Pattern urlOrRegExp) {
|
||||
hasURL(urlOrRegExp, null);
|
||||
}
|
||||
/**
|
||||
* Ensures the page is navigated to the given URL.
|
||||
* <pre>{@code
|
||||
* assertThat(page).hasURL(".com");
|
||||
* }</pre>
|
||||
*
|
||||
* @param urlOrRegExp Expected substring or RegExp.
|
||||
*/
|
||||
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
|
||||
/**
|
||||
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
|
||||
* {@code "error"}:
|
||||
* <pre>{@code
|
||||
* assertThat(page).not().hasURL("error");
|
||||
* }</pre>
|
||||
*/
|
||||
PageAssertions not();
|
||||
}
|
||||
|
||||
+79
@@ -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.assertions;
|
||||
|
||||
import java.util.*;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
|
||||
/**
|
||||
* The {@code PlaywrightAssertions} class provides convenience methods for creating assertions that will wait until the expected
|
||||
* condition is met.
|
||||
*
|
||||
* <p> Consider the following example:
|
||||
* <pre>{@code
|
||||
* ...
|
||||
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
*
|
||||
* public class TestExample {
|
||||
* ...
|
||||
* @Test
|
||||
* void statusBecomesSubmitted() {
|
||||
* ...
|
||||
* page.click("#submit-button");
|
||||
* assertThat(page.locator(".status")).hasText("Submitted");
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"} text. It
|
||||
* will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is reached.
|
||||
* You can pass this timeout as an option.
|
||||
*
|
||||
* <p> By default, the timeout for assertions is set to 5 seconds.
|
||||
*
|
||||
* <p> To use Playwright assertions add the following dependency into the {@code pom.xml} of your Maven project:
|
||||
*/
|
||||
public interface PlaywrightAssertions {
|
||||
/**
|
||||
* Creates a {@code LocatorAssertions} object for the given {@code Locator}.
|
||||
* <pre>{@code
|
||||
* PlaywrightAssertions.assertThat(locator).isVisible();
|
||||
* }</pre>
|
||||
*
|
||||
* @param locator {@code Locator} object to use for assertions.
|
||||
*/
|
||||
static LocatorAssertions assertThat(Locator locator) {
|
||||
return new LocatorAssertionsImpl(locator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PageAssertions} object for the given {@code Page}.
|
||||
* <pre>{@code
|
||||
* PlaywrightAssertions.assertThat(page).hasTitle("News");
|
||||
* }</pre>
|
||||
*
|
||||
* @param page {@code Page} object to use for assertions.
|
||||
*/
|
||||
static PageAssertions assertThat(Page page) {
|
||||
return new PageAssertionsImpl(page);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
import org.opentest4j.ValueWrapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class AssertionsBase {
|
||||
final LocatorImpl actualLocator;
|
||||
final boolean isNot;
|
||||
|
||||
AssertionsBase(LocatorImpl actual, boolean isNot) {
|
||||
this.actualLocator = actual;
|
||||
this.isNot = isNot;
|
||||
}
|
||||
|
||||
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
|
||||
expectImpl(expression, asList(textValue), expected, message, options);
|
||||
}
|
||||
|
||||
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
|
||||
if (options == null) {
|
||||
options = new FrameExpectOptions();
|
||||
}
|
||||
options.expectedText = expectedText;
|
||||
options.isNot = isNot;
|
||||
expectImpl(expression, options, expected, message);
|
||||
}
|
||||
|
||||
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
|
||||
if (expectOptions.timeout == null) {
|
||||
expectOptions.timeout = 5_000.0;
|
||||
}
|
||||
if (expectOptions.isNot) {
|
||||
message = message.replace("expected to", "expected not to");
|
||||
}
|
||||
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
|
||||
if (result.matches == isNot) {
|
||||
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
|
||||
String log = String.join("\n", result.log);
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + log;
|
||||
}
|
||||
if (expected == null) {
|
||||
throw new AssertionFailedError(message + log);
|
||||
}
|
||||
throw new AssertionFailedError(message + log, formatValue(expected), formatValue(actual));
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueWrapper formatValue(Object value) {
|
||||
if (value == null || !value.getClass().isArray()) {
|
||||
return ValueWrapper.create(value);
|
||||
}
|
||||
Collection<String> values = asList((Object[]) value).stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||
String stringRepresentation = "[" + String.join(", ", values) + "]";
|
||||
return ValueWrapper.create(value, stringRepresentation);
|
||||
}
|
||||
|
||||
static ExpectedTextValue expectedRegex(Pattern pattern) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.regexSource = pattern.pattern();
|
||||
if (pattern.flags() != 0) {
|
||||
expected.regexFlags = "";
|
||||
if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) {
|
||||
// Case-insensitive search.
|
||||
expected.regexFlags += "i";
|
||||
}
|
||||
if ((pattern.flags() & Pattern.DOTALL) != 0) {
|
||||
// Allows . to match newline characters.
|
||||
expected.regexFlags += "s";
|
||||
}
|
||||
if ((pattern.flags() & Pattern.MULTILINE) != 0) {
|
||||
// Multi-line search.
|
||||
expected.regexFlags += "m";
|
||||
}
|
||||
if ((pattern.flags() & ~(Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL)) != 0) {
|
||||
throw new PlaywrightException("Unexpected RegEx flag, only CASE_INSENSITIVE, DOTALL and MULTILINE are supported.");
|
||||
}
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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.Locator;
|
||||
import com.microsoft.playwright.assertions.LocatorAssertions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.serializeArgument;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions {
|
||||
public LocatorAssertionsImpl(Locator locator) {
|
||||
this(locator, false);
|
||||
}
|
||||
|
||||
private LocatorAssertionsImpl(Locator locator, boolean isNot) {
|
||||
super((LocatorImpl) locator, isNot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsText(String text, ContainsTextOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsText(Pattern pattern, ContainsTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsText(String[] strings, ContainsTextOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (String text : strings) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsText(Pattern[] patterns, ContainsTextOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasAttribute(String name, String text, HasAttributeOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
hasAttribute(name, expected, text, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
hasAttribute(name, expected, pattern, options);
|
||||
}
|
||||
|
||||
private void hasAttribute(String name, ExpectedTextValue expectedText, Object expectedValue, HasAttributeOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasAttributeOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertViaJson(options, FrameExpectOptions.class);
|
||||
commonOptions.expressionArg = name;
|
||||
String message = "Locator expected to have attribute '" + name + "'";
|
||||
if (expectedValue instanceof Pattern) {
|
||||
message += " matching regex";
|
||||
}
|
||||
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasClass(String text, HasClassOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasClass(Pattern pattern, HasClassOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasClass(String[] strings, HasClassOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (String text : strings) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasClass(Pattern[] patterns, HasClassOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasCount(int count, HasCountOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasCountOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertViaJson(options, FrameExpectOptions.class);
|
||||
commonOptions.expectedNumber = count;
|
||||
List<ExpectedTextValue> expectedText = null;
|
||||
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasCSS(String name, String value, HasCSSOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = value;
|
||||
hasCSS(name, expected, value, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasCSS(String name, Pattern pattern, HasCSSOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
hasCSS(name, expected, pattern, options);
|
||||
}
|
||||
|
||||
private void hasCSS(String name, ExpectedTextValue expectedText, Object expectedValue, HasCSSOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasCSSOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertViaJson(options, FrameExpectOptions.class);
|
||||
commonOptions.expressionArg = name;
|
||||
String message = "Locator expected to have CSS property '" + name + "'";
|
||||
if (expectedValue instanceof Pattern) {
|
||||
message += " matching regex";
|
||||
}
|
||||
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasId(String id, HasIdOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = id;
|
||||
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasJSProperty(String name, Object value, HasJSPropertyOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasJSPropertyOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertViaJson(options, FrameExpectOptions.class);
|
||||
commonOptions.expressionArg = name;
|
||||
commonOptions.expectedValue = serializeArgument(value);
|
||||
List<ExpectedTextValue> list = null;
|
||||
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasText(String text, HasTextOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasText(Pattern pattern, HasTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
// Just match substring, same as containsText.
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasText(String[] strings, HasTextOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (String text : strings) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasText(Pattern[] patterns, HasTextOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValue(String value, HasValueOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = value;
|
||||
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValue(Pattern pattern, HasValueOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isChecked(IsCheckedOptions options) {
|
||||
expectTrue("to.be.checked", "Locator expected to be checked", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isDisabled(IsDisabledOptions options) {
|
||||
expectTrue("to.be.disabled", "Locator expected to be disabled", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEditable(IsEditableOptions options) {
|
||||
expectTrue("to.be.editable", "Locator expected to be editable", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEmpty(IsEmptyOptions options) {
|
||||
expectTrue("to.be.empty", "Locator expected to be empty", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEnabled(IsEnabledOptions options) {
|
||||
expectTrue("to.be.enabled", "Locator expected to be enabled", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isFocused(IsFocusedOptions options) {
|
||||
expectTrue("to.be.focused", "Locator expected to be focused", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isHidden(IsHiddenOptions options) {
|
||||
expectTrue("to.be.hidden", "Locator expected to be hidden", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isVisible(IsVisibleOptions options) {
|
||||
expectTrue("to.be.visible", "Locator expected to be visible", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
private void expectTrue(String expression, String message, FrameExpectOptions options) {
|
||||
List<ExpectedTextValue> expectedText = null;
|
||||
expectImpl(expression, expectedText, null, message, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatorAssertions not() {
|
||||
return new LocatorAssertionsImpl(actualLocator, !isNot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.Page;
|
||||
import com.microsoft.playwright.assertions.PageAssertions;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
public class PageAssertionsImpl extends AssertionsBase implements PageAssertions {
|
||||
private final PageImpl actualPage;
|
||||
|
||||
public PageAssertionsImpl(Page page) {
|
||||
this(page, false);
|
||||
}
|
||||
|
||||
private PageAssertionsImpl(Page page, boolean isNot) {
|
||||
super((LocatorImpl) page.locator(":root"), isNot);
|
||||
this.actualPage = (PageImpl) page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasTitle(String title, HasTitleOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = title;
|
||||
expectImpl("to.have.title", expected, title, "Page title expected to be", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasTitle(Pattern pattern, HasTitleOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasURL(String url, HasURLOptions options) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
if (actualPage.context().baseUrl != null) {
|
||||
url = resolveUrl(actualPage.context().baseUrl, url);
|
||||
}
|
||||
expected.string = url;
|
||||
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasURL(Pattern pattern, HasURLOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertViaJson(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAssertions not() {
|
||||
return new PageAssertionsImpl(actualPage, !isNot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,826 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.assertions.LocatorAssertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestLocatorAssertions extends TestBase {
|
||||
@Test
|
||||
void containsTextWRegexPass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).containsText(Pattern.compile("ex"));
|
||||
// Should not normalize whitespace.
|
||||
assertThat(locator).containsText(Pattern.compile("ext cont"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsTextWRegexCaseInsensitivePass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).containsText(Pattern.compile("text", Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsTextWRegexMultilinePass() {
|
||||
page.setContent("<div id=node>Text \nContent</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).containsText(Pattern.compile("^Content", Pattern.MULTILINE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsTextWRegexDotAllPass() {
|
||||
page.setContent("<div id=node>foo\nbar</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).containsText(Pattern.compile("foo.bar", Pattern.DOTALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsTextWRegexFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).containsText(Pattern.compile("ex2"), new LocatorAssertions.ContainsTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("ex2", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Text content", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Locator expected to contain regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWRegexPass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasText(Pattern.compile("Te.t"));
|
||||
// Should not normalize whitespace.
|
||||
assertThat(locator).hasText(Pattern.compile("Text.+content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWRegexFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasText(Pattern.compile("Text 2"), new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("Text 2", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Text content", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have text matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextPass() {
|
||||
page.setContent("<div id=node><span></span>Text \ncontent </div>");
|
||||
Locator locator = page.locator("#node");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText("Text content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
// Should normalize whitespace.
|
||||
try {
|
||||
assertThat(locator).hasText("Text", new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("Text", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Text content", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPass() {
|
||||
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
|
||||
Locator locator = page.locator("div");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new String[] {"Text 1", "Text 2a"});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPassEmpty() {
|
||||
page.setContent("<div></div>");
|
||||
Locator locator = page.locator("p");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new String[] {});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPassNotEmpty() {
|
||||
page.setContent("<div><p>Test</p></div>");
|
||||
Locator locator = page.locator("div");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).not().hasText(new String[] {});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPassOnEmpty() {
|
||||
page.setContent("<div></div>");
|
||||
Locator locator = page.locator("p");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).not().hasText(new String[] {"Test"});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayFailOnNotEmpty() {
|
||||
page.setContent("<div></div>");
|
||||
Locator locator = page.locator("p");
|
||||
// Should normalize whitespace.
|
||||
try {
|
||||
assertThat(locator).not().hasText(new String[] {}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("[]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("null", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to have text"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPassLazyPass() {
|
||||
page.setContent("<div id=div></div>");
|
||||
Locator locator = page.locator("p");
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" div.innerHTML = \"<p>Text 1</p><p>Text 2</p>\";\n" +
|
||||
"}, 100);");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new String[] {"Text 1", "Text 2"}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayFail() {
|
||||
page.setContent("<div>Text 1</div><div>Text 3</div>");
|
||||
Locator locator = page.locator("div");
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" div.innerHTML = \"<p>Text 1</p><p>Text 2</p>\";\n" +
|
||||
"}, 100);");
|
||||
try {
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new String[] {"Text 1", "Text 3", "Extra"}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWRegExArrayPass() {
|
||||
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
|
||||
Locator locator = page.locator("div");
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new Pattern[] {Pattern.compile( "Text \n1"), Pattern.compile("Text \\d+a")});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWRegExArrayFail() {
|
||||
page.setContent("<div>Text 1</div><div>Text 3</div>");
|
||||
Locator locator = page.locator("div");
|
||||
try {
|
||||
// Should normalize whitespace.
|
||||
assertThat(locator).hasText(new Pattern[] {Pattern.compile( "Text 1"), Pattern.compile("Text \\d"), Pattern.compile("Extra")}, new LocatorAssertions.HasTextOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("[Text 1, Text \\d, Extra]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAttributeTextPass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasAttribute("id", "node");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAttributeTextFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasAttribute("id", "foo", new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo", e.getExpected().getStringRepresentation());
|
||||
assertEquals("node", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id'"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAttributeRegExpPass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasAttribute("id", Pattern.compile("n..e"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAttributeRegExpFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasAttribute("id", Pattern.compile(".Nod.."), new LocatorAssertions.HasAttributeOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
|
||||
assertEquals("node", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassTextPass() {
|
||||
page.setContent("<div class=\"foo bar baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
assertThat(locator).hasClass("foo bar baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassTextFail() {
|
||||
page.setContent("<div class=\"bar baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
try {
|
||||
assertThat(locator).hasClass("foo bar baz", new LocatorAssertions.HasClassOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo bar baz", e.getExpected().getStringRepresentation());
|
||||
assertEquals("bar baz", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassRegExpPass() {
|
||||
page.setContent("<div class=\"foo bar baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
assertThat(locator).hasClass(Pattern.compile("foo.* baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassRegExpFail() {
|
||||
page.setContent("<div class=\"bar baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
try {
|
||||
assertThat(locator).hasClass(Pattern.compile("foo Z.*"), new LocatorAssertions.HasClassOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo Z.*", e.getExpected().getStringRepresentation());
|
||||
assertEquals("bar baz", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassTextArrayPass() {
|
||||
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
assertThat(locator).hasClass(new String[] {"foo", "bar", "baz"});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassTextArrayFail() {
|
||||
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
try {
|
||||
assertThat(locator).hasClass(new String[] {"foo", "bar", "missing"}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("[foo, bar, missing]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have class"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassRegExpArrayPass() {
|
||||
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
assertThat(locator).hasClass(new Pattern[] {Pattern.compile("fo.*"), Pattern.compile(".ar"), Pattern.compile("baz")});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasClassRegExpArrayFail() {
|
||||
page.setContent("<div class=\"foo\"></div><div class=\"bar\"></div><div class=\"baz\"></div>");
|
||||
Locator locator = page.locator("div");
|
||||
try {
|
||||
assertThat(locator).hasClass(new Pattern[] {Pattern.compile("fo.*"), Pattern.compile(".ar"), Pattern.compile("baz"), Pattern.compile("extra")}, new LocatorAssertions.HasClassOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("[fo.*, .ar, baz, extra]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("[foo, bar, baz]", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have class matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCountPass() {
|
||||
page.setContent("<select><option>One</option><option>Two</option></select>");
|
||||
Locator locator = page.locator("option");
|
||||
assertThat(locator).hasCount(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCountFail() {
|
||||
page.setContent("<select><option>One</option><option>Two</option></select>");
|
||||
Locator locator = page.locator("option");
|
||||
try {
|
||||
assertThat(locator).hasCount(1, new LocatorAssertions.HasCountOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("1", e.getExpected().getStringRepresentation());
|
||||
assertEquals("2", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have count"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCountPassZero() {
|
||||
page.setContent("<div></div>");
|
||||
Locator locator = page.locator("span");
|
||||
assertThat(locator).hasCount(0);
|
||||
assertThat(locator).not().hasCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCSSPass() {
|
||||
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasCSS("color", "rgb(255, 0, 0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCSSFail() {
|
||||
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasCSS("color", "red", new LocatorAssertions.HasCSSOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("red", e.getExpected().getStringRepresentation());
|
||||
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color'"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCSSRegExPass() {
|
||||
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasCSS("color", Pattern.compile("rgb.*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasCSSRegExFail() {
|
||||
page.setContent("<div id=node style='color: rgb(255, 0, 0)'>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasCSS("color", Pattern.compile("red"), new LocatorAssertions.HasCSSOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("red", e.getExpected().getStringRepresentation());
|
||||
assertEquals("rgb(255, 0, 0)", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have CSS property 'color' matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasIdPass() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasId("node");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasIdFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasId("foo", new LocatorAssertions.HasIdOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo", e.getExpected().getStringRepresentation());
|
||||
assertEquals("node", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have ID"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasJSPropertyPass() {
|
||||
page.setContent("<div></div>");
|
||||
page.evalOnSelector("div", "e => e.foo = { a: 1, b: 'string' }");
|
||||
Locator locator = page.locator("div");
|
||||
assertThat(locator).hasJSProperty("foo", mapOf("a", 1, "b", "string"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasJSPropertyNumberFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
page.evalOnSelector("div", "e => e.foo = 2021");
|
||||
try {
|
||||
assertThat(locator).hasJSProperty("foo", 1, new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("1", e.getExpected().getStringRepresentation());
|
||||
assertEquals("2021", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasJSPropertyObjectFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
page.evalOnSelector("div", "e => e.foo = { a: 1, b: 'string' }");
|
||||
try {
|
||||
assertThat(locator).hasJSProperty("foo", mapOf("a", 2), new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("{a=2}", e.getExpected().getStringRepresentation());
|
||||
assertEquals("{a=1, b=string}", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'foo'"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasJSPropertyStringFail() {
|
||||
page.setContent("<div id=node>Text content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
try {
|
||||
assertThat(locator).hasJSProperty("id", "foo", new LocatorAssertions.HasJSPropertyOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo", e.getExpected().getStringRepresentation());
|
||||
assertEquals("node", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have JavaScript property 'id'"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasValueTextPass() {
|
||||
page.setContent("<input id=node></input>");
|
||||
Locator locator = page.locator("#node");
|
||||
locator.fill("Text content");
|
||||
assertThat(locator).hasValue("Text content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasValueTextFail() {
|
||||
page.setContent("<input id=node></input>");
|
||||
Locator locator = page.locator("#node");
|
||||
locator.fill("Text content");
|
||||
try {
|
||||
assertThat(locator).hasValue("Text2", new LocatorAssertions.HasValueOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("Text2", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Text content", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have value"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasValueRegExpPass() {
|
||||
page.setContent("<input id=node></input>");
|
||||
Locator locator = page.locator("#node");
|
||||
locator.fill("Text content");
|
||||
assertThat(locator).hasValue(Pattern.compile("Text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasValueRegExpPassWithNot() {
|
||||
page.setContent("<input id=node></input>");
|
||||
Locator locator = page.locator("#node");
|
||||
locator.fill("Text content");
|
||||
assertThat(locator).not().hasValue(Pattern.compile("Text2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasValueRegExpFail() {
|
||||
page.setContent("<input id=node></input>");
|
||||
Locator locator = page.locator("#node");
|
||||
locator.fill("Text content");
|
||||
try {
|
||||
assertThat(locator).hasValue(Pattern.compile("Text2"), new LocatorAssertions.HasValueOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("Text2", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Text content", e.getActual().getStringRepresentation());
|
||||
assertTrue(e.getMessage().contains("Locator expected to have value matching regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCheckedPass() {
|
||||
page.setContent("<input type=checkbox checked></input>");
|
||||
Locator locator = page.locator("input");
|
||||
assertThat(locator).isChecked();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCheckedFail() {
|
||||
page.setContent("<input type=checkbox></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be checked"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsCheckedFail() {
|
||||
page.setContent("<input type=checkbox checked></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).not().isChecked(new LocatorAssertions.IsCheckedOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be checked"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isDisabledPass() {
|
||||
page.setContent("<button disabled>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
assertThat(locator).isDisabled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isDisabledFail() {
|
||||
page.setContent("<button>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be disabled"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsDisabledFail() {
|
||||
page.setContent("<button disabled>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).not().isDisabled(new LocatorAssertions.IsDisabledOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be disabled"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEditablePass() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
assertThat(locator).isEditable();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEditableFail() {
|
||||
page.setContent("<input disabled></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be editable"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsEditableFail() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).not().isEditable(new LocatorAssertions.IsEditableOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be editable"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void isEmptyPass() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
assertThat(locator).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyFail() {
|
||||
page.setContent("<input value=text></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be empty"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsEmptyFail() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).not().isEmpty(new LocatorAssertions.IsEmptyOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be empty"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEnabledPass() {
|
||||
page.setContent("<button>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
assertThat(locator).isEnabled();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEnabledFail() {
|
||||
page.setContent("<button disabled>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be enabled"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsEnabledFail() {
|
||||
page.setContent("<button>Text</button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).not().isEnabled(new LocatorAssertions.IsEnabledOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be enabled"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isFocusedPass() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
locator.focus();
|
||||
assertThat(locator).isFocused();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isFocusedFail() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be focused"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsFocusedFail() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
locator.focus();
|
||||
try {
|
||||
assertThat(locator).not().isFocused(new LocatorAssertions.IsFocusedOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be focused"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isHiddenPass() {
|
||||
page.setContent("<button style='display: none'></button>");
|
||||
Locator locator = page.locator("button");
|
||||
assertThat(locator).isHidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isHiddenFail() {
|
||||
page.setContent("<button></button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be hidden"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsHiddenFail() {
|
||||
page.setContent("<button style='display: none'></button>");
|
||||
Locator locator = page.locator("button");
|
||||
try {
|
||||
assertThat(locator).not().isHidden(new LocatorAssertions.IsHiddenOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be hidden"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isVisiblePass() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
assertThat(locator).isVisible();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isVisibleFail() {
|
||||
page.setContent("<input style='display: none'></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected to be visible"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notIsVisibleFail() {
|
||||
page.setContent("<input></input>");
|
||||
Locator locator = page.locator("input");
|
||||
try {
|
||||
assertThat(locator).not().isVisible(new LocatorAssertions.IsVisibleOptions().setTimeout(1000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertNull(e.getExpected());
|
||||
assertNull(e.getActual());
|
||||
assertTrue(e.getMessage().contains("Locator expected not to be visible"), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestLocatorFrame extends TestBase {
|
||||
private static void routeIframe(Page page) {
|
||||
page.route("**/empty.html", route -> route.fulfill(new Route.FulfillOptions()
|
||||
.setBody("<iframe src='iframe.html'></iframe>").setContentType("text/html")));
|
||||
page.route("**/iframe.html", route -> {
|
||||
route.fulfill(new Route.FulfillOptions().setBody("<html>\n" +
|
||||
" <div>\n" +
|
||||
" <button>Hello iframe</button>\n" +
|
||||
" <iframe src='iframe-2.html'></iframe>\n" +
|
||||
" </div>\n" +
|
||||
" <span>1</span>\n" +
|
||||
" <span>2</span>\n" +
|
||||
" </html>").setContentType("text/html"));
|
||||
});
|
||||
page.route("**/iframe-2.html", route -> {
|
||||
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello nested iframe</button></html>").setContentType("text/html"));
|
||||
});
|
||||
}
|
||||
|
||||
private static void routeAmbiguous(Page page) {
|
||||
page.route("**/empty.html", route -> {
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setBody("<iframe src='iframe-1.html'></iframe>\n" +
|
||||
"<iframe src='iframe-2.html'></iframe>\n" +
|
||||
"<iframe src='iframe-3.html'></iframe>")
|
||||
.setContentType("text/html"));
|
||||
});
|
||||
page.route("**/iframe-*", route -> {
|
||||
try {
|
||||
String path = new URL(route.request().url()).getPath().substring(1);
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setBody("<html><button>Hello from " + path + "</button></html>")
|
||||
.setContentType("text/html"));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkForIframe() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.frameLocator("iframe").locator("button");
|
||||
button.waitFor();
|
||||
assertEquals("Hello iframe", button.innerText());
|
||||
assertThat(button).hasText("Hello iframe");
|
||||
button.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkForNestedIframe() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.frameLocator("iframe").frameLocator("iframe").locator("button");
|
||||
button.waitFor();
|
||||
assertEquals("Hello nested iframe", button.innerText());
|
||||
assertThat(button).hasText("Hello nested iframe");
|
||||
button.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkForAnd() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator locator = page.frameLocator("iframe").locator("button");
|
||||
assertThat(locator).hasText("Hello iframe");
|
||||
assertEquals("Hello iframe", locator.innerText());
|
||||
Locator spans = page.frameLocator("iframe").locator("span");
|
||||
assertThat(spans).hasCount(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWaitForFrame() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
try {
|
||||
page.frameLocator("iframe").locator("span").click(new Locator.ClickOptions().setTimeout(300));
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("waiting for frame \"iframe\""), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWaitForFrame2() {
|
||||
routeIframe(page);
|
||||
page.evaluate("url => setTimeout(() => location.href = url, 300)", server.EMPTY_PAGE);
|
||||
page.frameLocator("iframe").locator("button").click();
|
||||
}
|
||||
|
||||
void shouldWaitForFrameToGo() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWaitForFrame() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page.frameLocator("iframe").locator("span")).isHidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWaitForFrame2() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page.frameLocator("iframe").locator("span")).not().isVisible();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWaitForFrame3() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page.frameLocator("iframe").locator("span")).hasCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldClickInLazyIframe() {
|
||||
page.route("**/iframe.html", route -> {
|
||||
route.fulfill(new Route.FulfillOptions().setBody("<html><button>Hello iframe</button></html>").setContentType("text/html"));
|
||||
});
|
||||
// empty pge
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
|
||||
// add blank iframe
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" const iframe = document.createElement('iframe');\n" +
|
||||
" document.body.appendChild(iframe);\n" +
|
||||
" // navigate iframe\n" +
|
||||
" setTimeout(() => iframe.src = 'iframe.html', 500);\n" +
|
||||
" }, 500);");
|
||||
// Click in iframe
|
||||
Locator button = page.frameLocator("iframe").locator("button");
|
||||
button.click();
|
||||
assertThat(button).hasText("Hello iframe");
|
||||
assertEquals("Hello iframe", button.innerText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForShouldSurviveFrameReattach() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" document.querySelector('iframe').remove();\n" +
|
||||
" setTimeout(() => {\n" +
|
||||
" const iframe = document.createElement('iframe');\n" +
|
||||
" iframe.src = 'iframe-2.html';\n" +
|
||||
" document.body.appendChild(iframe);\n" +
|
||||
" }, 500);\n" +
|
||||
" }, 500);");
|
||||
button.waitFor();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clickShouldSurviveFrameReattach() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
|
||||
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" document.querySelector('iframe').remove();\n" +
|
||||
" setTimeout(() => {\n" +
|
||||
" const iframe = document.createElement('iframe');\n" +
|
||||
" iframe.src = 'iframe-2.html';\n" +
|
||||
" document.body.appendChild(iframe);\n" +
|
||||
" }, 500);\n" +
|
||||
" }, 500);");
|
||||
button.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clickShouldSurviveIframeNavigation() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.frameLocator("iframe").locator("button:has-text('Hello nested iframe')");
|
||||
page.evaluate("setTimeout(() => {\n" +
|
||||
" document.querySelector('iframe').src = 'iframe-2.html';\n" +
|
||||
" }, 500);");
|
||||
button.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNonWorkForNonFrame() {
|
||||
routeIframe(page);
|
||||
page.setContent("<div></div>");
|
||||
Locator button = page.frameLocator("div").locator("button");
|
||||
try {
|
||||
button.waitFor();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("<div></div>"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("<iframe> was expected"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void locatorFrameLocatorShouldWorkForIframe() {
|
||||
routeIframe(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.locator("body").frameLocator("iframe").locator("button");
|
||||
button.waitFor();
|
||||
assertThat(button).hasText("Hello iframe");
|
||||
assertEquals("Hello iframe", button.innerText());
|
||||
button.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void locatorFrameLocatorShouldThrowOnAmbiguity() {
|
||||
routeAmbiguous(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button = page.locator("body").frameLocator("iframe").locator("button");
|
||||
try {
|
||||
button.waitFor();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Error: strict mode violation: \"body >> iframe\" resolved to 3 elements"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void locatorFrameLocatorShouldNotThrowOnFirstLastNth() {
|
||||
routeAmbiguous(page);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Locator button1 = page.locator("body").frameLocator("iframe").first().locator("button");
|
||||
assertThat(button1).hasText("Hello from iframe-1.html");
|
||||
Locator button2 = page.locator("body").frameLocator("iframe").nth(1).locator("button");
|
||||
assertThat(button2).hasText("Hello from iframe-2.html");
|
||||
Locator button3 = page.locator("body").frameLocator("iframe").last().locator("button");
|
||||
assertThat(button3).hasText("Hello from iframe-3.html");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.assertions.PageAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestPageAssertions extends TestBase {
|
||||
@Test
|
||||
void hasURLTextPass() {
|
||||
page.navigate("data:text/html,<div>A</div>");
|
||||
assertThat(page).hasURL("data:text/html,<div>A</div>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasURLTextFail() {
|
||||
page.navigate("data:text/html,<div>B</div>");
|
||||
try {
|
||||
assertThat(page).hasURL("foo", new PageAssertions.HasURLOptions().setTimeout(1_000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo", e.getExpected().getValue());
|
||||
assertEquals("data:text/html,<div>B</div>", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Page URL expected to be"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportHasUrlWithBaseUrl() {
|
||||
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) {
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertThat(page).hasURL("/empty.html", new PageAssertions.HasURLOptions().setTimeout(1_000));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notHasUrlText() {
|
||||
page.navigate("data:text/html,<div>B</div>");
|
||||
assertThat(page).not().hasURL("about:blank", new PageAssertions.HasURLOptions().setTimeout(1000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasURLRegexPass() {
|
||||
page.navigate("data:text/html,<div>A</div>");
|
||||
assertThat(page).hasURL(Pattern.compile("text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasURLRegexFail() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
try {
|
||||
assertThat(page).hasURL(Pattern.compile(".*foo.*"), new PageAssertions.HasURLOptions().setTimeout(1_000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals(".*foo.*", e.getExpected().getStringRepresentation());
|
||||
assertEquals(server.EMPTY_PAGE, e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Page URL expected to match regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notHasUrlRegEx() {
|
||||
page.navigate("data:text/html,<div>B</div>");
|
||||
assertThat(page).not().hasURL(Pattern.compile("about"), new PageAssertions.HasURLOptions().setTimeout(1000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTitleTextPass() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
assertThat(page).hasTitle("Woof-Woof", new PageAssertions.HasTitleOptions().setTimeout(1_000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTitleTextFail() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
try {
|
||||
assertThat(page).hasTitle("foo", new PageAssertions.HasTitleOptions().setTimeout(1_000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("foo", e.getExpected().getValue());
|
||||
assertEquals("Woof-Woof", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Page title expected to be"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTitleRegexPass() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
assertThat(page).hasTitle(Pattern.compile("^.oof.+oof$"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTitleRegexFail() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
try {
|
||||
assertThat(page).hasTitle(Pattern.compile("^foo[AB]"), new PageAssertions.HasTitleOptions().setTimeout(1_000));
|
||||
fail("did not throw");
|
||||
} catch (AssertionFailedError e) {
|
||||
assertEquals("^foo[AB]", e.getExpected().getStringRepresentation());
|
||||
assertEquals("Woof-Woof", e.getActual().getValue());
|
||||
assertTrue(e.getMessage().contains("Page title expected to match regex"), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void notHasTitleRegEx() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
assertThat(page).not().hasTitle(Pattern.compile("ab.ut"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTitleRegExCaseInsensitivePass() {
|
||||
page.navigate(server.PREFIX + "/title.html");
|
||||
assertThat(page).hasTitle(Pattern.compile("woof-woof", Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
}
|
||||
+2
-15
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.17.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
<name>Playwright - Drivers For All Platforms</name>
|
||||
<description>
|
||||
This module includes playwright-cli binary and related utilities for all supported platforms.
|
||||
This module includes Playwright driver and related utilities for all supported platforms.
|
||||
It is intended to be used on the systems where Playwright driver is not preinstalled.
|
||||
</description>
|
||||
|
||||
@@ -28,23 +28,10 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
</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,16 +100,38 @@ 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")) {
|
||||
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
|
||||
return "win32_x64";
|
||||
}
|
||||
if (name.contains("linux")) {
|
||||
return "linux";
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-14
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.17.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
<name>Playwright - Driver</name>
|
||||
<description>
|
||||
This module provides API for discovery and launching of playwright-cli binary.
|
||||
This module provides API for discovery and launching of Playwright driver.
|
||||
</description>
|
||||
|
||||
<build>
|
||||
@@ -24,18 +24,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -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.17.2</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.16.0</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": {}
|
||||
}
|
||||
+19
-14
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>0.190.0-SNAPSHOT</version>
|
||||
<version>1.17.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
@@ -31,17 +31,7 @@
|
||||
<configuration>
|
||||
<subpackages>com.microsoft.playwright</subpackages>
|
||||
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
|
||||
<additionalOptions>--allow-script-in-comments</additionalOptions>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -50,11 +40,26 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
<targetPath>resources</targetPath>
|
||||
</testResource>
|
||||
</testResources>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
@@ -74,9 +86,17 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Double deviceScaleFactor;
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
* An object containing additional HTTP headers to be sent with every request.
|
||||
*/
|
||||
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.
|
||||
@@ -87,7 +107,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public HttpCredentials httpCredentials;
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean ignoreHTTPSErrors;
|
||||
/**
|
||||
@@ -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.
|
||||
*/
|
||||
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 when sending network requests. 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;
|
||||
/**
|
||||
@@ -294,9 +510,17 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Double deviceScaleFactor;
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
* An object containing additional HTTP headers to be sent with every request.
|
||||
*/
|
||||
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.
|
||||
@@ -307,7 +531,7 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public HttpCredentials httpCredentials;
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean ignoreHTTPSErrors;
|
||||
/**
|
||||
@@ -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.
|
||||
*/
|
||||
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 when sending network requests. 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.
|
||||
*/
|
||||
|
||||
@@ -29,15 +29,15 @@ import java.util.regex.Pattern;
|
||||
* <p> If a page opens another page, e.g. with a {@code window.open} call, the popup will belong to the parent page's browser
|
||||
* context.
|
||||
*
|
||||
* <p> Playwright allows creation of "incognito" browser contexts with {@code browser.newContext()} method. "Incognito" browser
|
||||
* contexts don't write any browsing data to disk.
|
||||
* <p> Playwright allows creating "incognito" browser contexts with {@link Browser#newContext Browser.newContext()} method.
|
||||
* "Incognito" browser contexts don't write any browsing data to disk.
|
||||
* <pre>{@code
|
||||
* // Create a new incognito browser context
|
||||
* BrowserContext context = browser.newContext();
|
||||
* // 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.
|
||||
* An object containing additional HTTP headers to be sent with every request.
|
||||
*/
|
||||
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}.
|
||||
@@ -298,7 +485,7 @@ public interface BrowserType {
|
||||
*/
|
||||
public List<String> ignoreDefaultArgs;
|
||||
/**
|
||||
* Whether to ignore HTTPS errors during navigation. Defaults to {@code false}.
|
||||
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean ignoreHTTPSErrors;
|
||||
/**
|
||||
@@ -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.
|
||||
*/
|
||||
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 when sending network requests. 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.
|
||||
*/
|
||||
@@ -620,7 +1079,8 @@ public interface BrowserType {
|
||||
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
|
||||
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
|
||||
* empty string to use a temporary directory instead.
|
||||
*/
|
||||
default BrowserContext launchPersistentContext(Path userDataDir) {
|
||||
return launchPersistentContext(userDataDir, null);
|
||||
@@ -634,7 +1094,8 @@ public interface BrowserType {
|
||||
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
|
||||
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
|
||||
* empty string to use a temporary directory instead.
|
||||
*/
|
||||
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
|
||||
/**
|
||||
|
||||
@@ -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
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
|
||||
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
|
||||
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
|
||||
* <pre>{@code
|
||||
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
|
||||
* locator.click();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> **Strictness**
|
||||
*
|
||||
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
|
||||
* given selector.
|
||||
* <pre>{@code
|
||||
* // Throws if there are several frames in DOM:
|
||||
* page.frame_locator(".result-frame").locator("button").click();
|
||||
*
|
||||
* // Works because we explicitly tell locator to pick the first frame:
|
||||
* page.frame_locator(".result-frame").first().locator("button").click();
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FrameLocator {
|
||||
/**
|
||||
* Returns locator to the first matching frame.
|
||||
*/
|
||||
FrameLocator first();
|
||||
/**
|
||||
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
|
||||
* that iframe.
|
||||
*
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
|
||||
* selectors</a> for more details.
|
||||
*/
|
||||
FrameLocator frameLocator(String selector);
|
||||
/**
|
||||
* Returns locator to the last matching frame.
|
||||
*/
|
||||
FrameLocator last();
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
*
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
|
||||
* selectors</a> for more details.
|
||||
*/
|
||||
Locator locator(String selector);
|
||||
/**
|
||||
* Returns locator to the n-th matching frame.
|
||||
*/
|
||||
FrameLocator nth(int index);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -57,11 +62,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -92,11 +97,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -125,11 +130,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
@@ -160,11 +165,11 @@ public interface Selectors {
|
||||
* Page page = browser.newPage();
|
||||
* page.setContent("<div><button>Click me</button></div>");
|
||||
* // Use the selector prefixed with its name.
|
||||
* ElementHandle button = page.querySelector("tag=button");
|
||||
* Locator button = page.locator("tag=button");
|
||||
* // Combine it with other selector engines.
|
||||
* page.click("tag=div >> text=\"Click me\"");
|
||||
* // Can use it in any methods supporting selectors.
|
||||
* int buttonCount = (int) page.evalOnSelectorAll("tag=button", "buttons => buttons.length");
|
||||
* int buttonCount = (int) page.locator("tag=button").count();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*
|
||||
|
||||
@@ -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,234 @@
|
||||
/*
|
||||
* 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;
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
public StartOptions setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StartChunkOptions {
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
public StartChunkOptions setTitle(String title) {
|
||||
this.title = title;
|
||||
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>
|
||||
*/
|
||||
default void startChunk() {
|
||||
startChunk(null);
|
||||
}
|
||||
/**
|
||||
* 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(StartChunkOptions options);
|
||||
/**
|
||||
* 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
|
||||
@@ -373,14 +457,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
withLogging("BrowserContext.unroute", () -> {
|
||||
routes.remove(matcher, handler);
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
maybeDisableNetworkInterception();
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeDisableNetworkInterception() {
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
}
|
||||
|
||||
void handleRoute(Route route) {
|
||||
boolean handled = routes.handle(route);
|
||||
if (handled) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
route.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
sendMessage("pause");
|
||||
}
|
||||
@@ -389,20 +486,63 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("route".equals(event)) {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
boolean handled = routes.handle(route);
|
||||
if (!handled) {
|
||||
route.resume();
|
||||
}
|
||||
handleRoute(route);
|
||||
} 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,13 +65,12 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
|
||||
private void closeImpl() {
|
||||
if (isRemote) {
|
||||
if (isConnectedOverWebSocket) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to close browser connection", e);
|
||||
}
|
||||
notifyRemoteClosed();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -167,12 +164,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 +180,34 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
return withLogging("Browser.newPage", () -> newPageImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startTracing(Page page, StartTracingOptions options) {
|
||||
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
|
||||
}
|
||||
|
||||
private void startTracingImpl(Page page, StartTracingOptions options) {
|
||||
if (options == null) {
|
||||
options = new StartTracingOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (page != null) {
|
||||
JsonObject jsonPage = new JsonObject();
|
||||
jsonPage.addProperty("guid", ((PageImpl) page).guid);
|
||||
params.add("page", jsonPage);
|
||||
}
|
||||
sendMessage("startTracing", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] stopTracing() {
|
||||
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
|
||||
}
|
||||
|
||||
private byte[] stopTracingImpl() {
|
||||
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
|
||||
return Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
}
|
||||
|
||||
private Page newPageImpl(NewPageOptions options) {
|
||||
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
|
||||
PageImpl page = context.newPage();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Browser;
|
||||
@@ -23,10 +24,7 @@ import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
@@ -56,39 +54,67 @@ 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();
|
||||
}
|
||||
};
|
||||
transport.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
playwright.sharedSelectors.removeChannel(selectors);
|
||||
transport.offClose(connectionCloseListener);
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
if (options == null) {
|
||||
options = new ConnectOptions();
|
||||
}
|
||||
// We don't use gson() here as the headers map should be serialized to a json object.
|
||||
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("wsEndpoint", wsEndpoint);
|
||||
JsonObject json = sendMessage("connect", params).getAsJsonObject();
|
||||
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
|
||||
Connection connection = new Connection(pipe);
|
||||
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<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
pipe.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
playwright.unregisterSelectors();
|
||||
pipe.offClose(connectionCloseListener);
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
});
|
||||
return browser;
|
||||
}
|
||||
|
||||
@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() {
|
||||
@@ -141,12 +167,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,14 +64,30 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
Connection(Transport transport) {
|
||||
if (isLogging) {
|
||||
transport = new TransportLogger(transport);
|
||||
}
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
@@ -88,6 +101,12 @@ public class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
String previous = apiName;
|
||||
apiName = name;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
transport.close();
|
||||
}
|
||||
@@ -148,20 +167,20 @@ 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);
|
||||
transport.send(message);
|
||||
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) {
|
||||
@@ -180,13 +199,13 @@ public class Connection {
|
||||
}
|
||||
|
||||
void processOneMessage() {
|
||||
String messageString = transport.poll(Duration.ofMillis(10));
|
||||
if (messageString == null) {
|
||||
JsonObject message = transport.poll(Duration.ofMillis(10));
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
Gson gson = gson();
|
||||
Message message = gson.fromJson(messageString, Message.class);
|
||||
dispatch(message);
|
||||
Message messageObj = gson.fromJson(message, Message.class);
|
||||
dispatch(messageObj);
|
||||
}
|
||||
|
||||
private void dispatch(Message message) {
|
||||
@@ -255,6 +274,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,21 +295,25 @@ 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 "APIRequestContext":
|
||||
// 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;
|
||||
case "JSHandle":
|
||||
result = new JSHandleImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "JsonPipe":
|
||||
result = new JsonPipe(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Page":
|
||||
result = new PageImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
@@ -297,9 +323,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,8 +28,10 @@ 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.options.WaitUntilState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
@@ -38,7 +40,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
private String url;
|
||||
FrameImpl parentFrame;
|
||||
Set<FrameImpl> childFrames = new LinkedHashSet<>();
|
||||
private final Set<LoadState> loadStates = new HashSet<>();
|
||||
private final Set<WaitUntilState> loadStates = new HashSet<>();
|
||||
|
||||
enum InternalEventType { NAVIGATED, LOADSTATE }
|
||||
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
|
||||
PageImpl page;
|
||||
@@ -58,22 +61,26 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
}
|
||||
|
||||
private static LoadState loadStateFromProtocol(String value) {
|
||||
private static WaitUntilState loadStateFromProtocol(String value) {
|
||||
switch (value) {
|
||||
case "load": return LOAD;
|
||||
case "domcontentloaded": return DOMCONTENTLOADED;
|
||||
case "networkidle": return NETWORKIDLE;
|
||||
case "commit": return COMMIT;
|
||||
default: throw new PlaywrightException("Unexpected value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@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 +138,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)));
|
||||
@@ -348,6 +358,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(this, selector);
|
||||
}
|
||||
|
||||
ElementHandle frameElementImpl() {
|
||||
JsonObject json = sendMessage("frameElement").getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
|
||||
@@ -405,6 +420,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 +465,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 +565,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatorImpl locator(String selector) {
|
||||
return new LocatorImpl(this, selector);
|
||||
}
|
||||
|
||||
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
@@ -599,6 +649,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,10 +800,17 @@ 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) {
|
||||
waitForLoadStateImpl(convertViaJson(state, WaitUntilState.class), options);
|
||||
}
|
||||
|
||||
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForLoadStateOptions();
|
||||
}
|
||||
@@ -755,11 +825,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
runUntil(() -> {}, new WaitableRace<>(waitables));
|
||||
}
|
||||
|
||||
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
|
||||
private final LoadState expectedState;
|
||||
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
|
||||
private final WaitUntilState expectedState;
|
||||
private boolean isDone;
|
||||
|
||||
WaitForLoadStateHelper(LoadState state) {
|
||||
WaitForLoadStateHelper(WaitUntilState state) {
|
||||
expectedState = state;
|
||||
isDone = loadStates.contains(state);
|
||||
if (!isDone) {
|
||||
@@ -768,7 +838,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(LoadState state) {
|
||||
public void accept(WaitUntilState state) {
|
||||
if (expectedState.equals(state)) {
|
||||
isDone = true;
|
||||
dispose();
|
||||
@@ -791,13 +861,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
|
||||
private final UrlMatcher matcher;
|
||||
private final LoadState expectedLoadState;
|
||||
private final WaitUntilState expectedLoadState;
|
||||
private WaitForLoadStateHelper loadStateHelper;
|
||||
|
||||
private RequestImpl request;
|
||||
private RuntimeException exception;
|
||||
|
||||
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
|
||||
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
|
||||
this.matcher = matcher;
|
||||
this.expectedLoadState = expectedLoadState;
|
||||
internalListeners.add(InternalEventType.NAVIGATED, this);
|
||||
@@ -856,10 +926,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,8 +942,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(options.url);
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
if (matcher == null) {
|
||||
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
|
||||
}
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||
@@ -882,11 +958,16 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
|
||||
return waitForSelectorImpl(selector, options, false);
|
||||
}
|
||||
|
||||
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options, boolean omitReturnValue) {
|
||||
if (options == null) {
|
||||
options = new WaitForSelectorOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
params.addProperty("omitReturnValue", omitReturnValue);
|
||||
JsonElement json = sendMessage("waitForSelector", params);
|
||||
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
|
||||
if (element == null) {
|
||||
@@ -901,20 +982,46 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
void waitForTimeoutImpl(double timeout) {
|
||||
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
|
||||
@Override
|
||||
public Void get() {
|
||||
// Override to not throw.
|
||||
return null;
|
||||
}
|
||||
});
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("timeout", timeout);
|
||||
sendMessage("waitForTimeout", params);
|
||||
}
|
||||
|
||||
@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(options.waitUntil, 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");
|
||||
if (add != null) {
|
||||
LoadState state = loadStateFromProtocol(add.getAsString());
|
||||
WaitUntilState state = loadStateFromProtocol(add.getAsString());
|
||||
loadStates.add(state);
|
||||
internalListeners.notify(InternalEventType.LOADSTATE, state);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.FrameLocator;
|
||||
|
||||
class FrameLocatorImpl implements FrameLocator {
|
||||
private final FrameImpl frame;
|
||||
private final String frameSelector;
|
||||
|
||||
FrameLocatorImpl(FrameImpl frame, String selector) {
|
||||
this.frame = frame;
|
||||
this.frameSelector = selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator first() {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocatorImpl frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator last() {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatorImpl locator(String selector) {
|
||||
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator nth(int index) {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class JsonPipe extends ChannelOwner implements Transport {
|
||||
private final Queue<JsonObject> incoming = new LinkedList<>();
|
||||
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
private enum EventType { CLOSE }
|
||||
private boolean isClosed;
|
||||
|
||||
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(JsonObject message) {
|
||||
checkIfClosed();
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("message", message);
|
||||
sendMessage("send", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject poll(Duration timeout) {
|
||||
Instant start = Instant.now();
|
||||
return runUntil(() -> {}, new Waitable<JsonObject>() {
|
||||
JsonObject message;
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
if (!incoming.isEmpty()) {
|
||||
message = incoming.remove();
|
||||
return true;
|
||||
}
|
||||
checkIfClosed();
|
||||
if (Duration.between(start, Instant.now()).compareTo(timeout) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject get() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!isClosed) {
|
||||
sendMessage("close");
|
||||
}
|
||||
}
|
||||
|
||||
void onClose(Consumer<JsonPipe> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
void offClose(Consumer<JsonPipe> handler) {
|
||||
listeners.remove(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("message".equals(event)) {
|
||||
incoming.add(params.get("message").getAsJsonObject());
|
||||
} else if ("closed".equals(event)) {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfClosed() {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Browser has been closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,440 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
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.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Serialization.serializeArgument;
|
||||
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 FrameLocatorImpl frameLocator(String selector) {
|
||||
return new FrameLocatorImpl(frame, this.selector + " >> " + selector);
|
||||
}
|
||||
|
||||
@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 void waitFor(WaitForOptions options) {
|
||||
if (options == null) {
|
||||
options = new WaitForOptions();
|
||||
}
|
||||
waitForImpl(options);
|
||||
}
|
||||
|
||||
private void waitForImpl(WaitForOptions options) {
|
||||
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Locator@" + selector;
|
||||
}
|
||||
|
||||
FrameExpectResult expect(String expression, FrameExpectOptions options) {
|
||||
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
|
||||
}
|
||||
|
||||
private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) {
|
||||
if (options == null) {
|
||||
options = new FrameExpectOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
params.addProperty("expression", expression);
|
||||
JsonElement json = frame.sendMessage("expect", params);
|
||||
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -204,14 +198,15 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
} else if ("route".equals(event)) {
|
||||
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
boolean handled = routes.handle(route);
|
||||
if (!handled) {
|
||||
handled = browserContext.routes.handle(route);
|
||||
}
|
||||
if (!handled) {
|
||||
route.resume();
|
||||
if (handled) {
|
||||
maybeDisableNetworkInterception();
|
||||
} else {
|
||||
browserContext.handleRoute(route);
|
||||
}
|
||||
} 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 +224,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 +444,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 +554,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 +565,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 +724,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
|
||||
@@ -708,6 +737,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return frameFor(new UrlMatcher(predicate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator frameLocator(String selector) {
|
||||
return mainFrame.frameLocator(selector);
|
||||
}
|
||||
|
||||
private Frame frameFor(UrlMatcher matcher) {
|
||||
for (Frame frame : frames) {
|
||||
if (matcher.test(frame.url())) {
|
||||
@@ -774,6 +808,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 +826,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 +878,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatorImpl locator(String selector) {
|
||||
return mainFrame.locator(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame mainFrame() {
|
||||
return mainFrame;
|
||||
@@ -843,14 +894,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 +954,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 +1053,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 +1169,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
|
||||
@@ -1131,33 +1185,40 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
withLogging("Page.unroute", () -> {
|
||||
routes.remove(matcher, handler);
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
maybeDisableNetworkInterception();
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeDisableNetworkInterception() {
|
||||
if (routes.size() == 0) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("enabled", false);
|
||||
sendMessage("setNetworkInterceptionEnabled", params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
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 +1242,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 +1312,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 +1322,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 +1333,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 +1371,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 +1385,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);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.*;
|
||||
@@ -24,8 +25,10 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
public class PipeTransport implements Transport {
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<JsonObject> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final BlockingQueue<String> outgoing= new ArrayBlockingQueue<>(1000);
|
||||
|
||||
private final ReaderThread readerThread;
|
||||
@@ -42,24 +45,36 @@ public class PipeTransport implements Transport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
public void send(JsonObject message) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
outgoing.put(message);
|
||||
// We could serialize the message on the IO thread but there is no guarantee
|
||||
// that the message object won't be modified on this thread after it's added
|
||||
// to the queue.
|
||||
outgoing.put(gson().toJson(message));
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String poll(Duration timeout) {
|
||||
public JsonObject poll(Duration timeout) {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
try {
|
||||
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
JsonObject 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);
|
||||
}
|
||||
@@ -82,8 +97,9 @@ public class PipeTransport implements Transport {
|
||||
|
||||
class ReaderThread extends Thread {
|
||||
private final DataInputStream in;
|
||||
private final BlockingQueue<String> queue;
|
||||
private final BlockingQueue<JsonObject> queue;
|
||||
volatile boolean isClosing;
|
||||
volatile Exception exception;
|
||||
|
||||
private static int readIntLE(DataInputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
@@ -97,7 +113,7 @@ class ReaderThread extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
ReaderThread(DataInputStream in, BlockingQueue<String> queue) {
|
||||
ReaderThread(DataInputStream in, BlockingQueue<JsonObject> queue) {
|
||||
this.in = in;
|
||||
this.queue = queue;
|
||||
}
|
||||
@@ -106,10 +122,11 @@ class ReaderThread extends Thread {
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
queue.put(readMessage());
|
||||
JsonObject message = gson().fromJson(readMessage(), JsonObject.class);
|
||||
queue.put(message);
|
||||
} 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
|
||||
|
||||
@@ -18,18 +18,12 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
class Binary {
|
||||
}
|
||||
import java.util.List;
|
||||
|
||||
class Channel {
|
||||
String guid;
|
||||
}
|
||||
|
||||
class Metadata{
|
||||
String stack;
|
||||
}
|
||||
|
||||
|
||||
class SerializedValue{
|
||||
Number n;
|
||||
Boolean b;
|
||||
@@ -51,45 +45,11 @@ class SerializedValue{
|
||||
Number h;
|
||||
}
|
||||
|
||||
|
||||
class SerializedArgument{
|
||||
SerializedValue value;
|
||||
Channel[] handles;
|
||||
}
|
||||
|
||||
class AXNode{
|
||||
String role;
|
||||
String name;
|
||||
String valueString;
|
||||
Number valueNumber;
|
||||
String description;
|
||||
String keyshortcuts;
|
||||
String roledescription;
|
||||
String valuetext;
|
||||
Boolean disabled;
|
||||
Boolean expanded;
|
||||
Boolean focused;
|
||||
Boolean modal;
|
||||
Boolean multiline;
|
||||
Boolean multiselectable;
|
||||
Boolean readonly;
|
||||
Boolean required;
|
||||
Boolean selected;
|
||||
// Possible values: { 'checked, 'unchecked, 'mixed }
|
||||
String checked;
|
||||
// Possible values: { 'pressed, 'released, 'mixed }
|
||||
String pressed;
|
||||
Number level;
|
||||
Number valuemin;
|
||||
Number valuemax;
|
||||
String autocomplete;
|
||||
String haspopup;
|
||||
String invalid;
|
||||
String orientation;
|
||||
AXNode[] children;
|
||||
}
|
||||
|
||||
|
||||
class SerializedError{
|
||||
public static class Error {
|
||||
String message;
|
||||
@@ -119,3 +79,28 @@ class SerializedError{
|
||||
}
|
||||
}
|
||||
|
||||
class ExpectedTextValue {
|
||||
String string;
|
||||
String regexSource;
|
||||
String regexFlags;
|
||||
Boolean matchSubstring;
|
||||
Boolean normalizeWhiteSpace;
|
||||
}
|
||||
|
||||
class FrameExpectOptions {
|
||||
Object expressionArg;
|
||||
List<ExpectedTextValue> expectedText;
|
||||
Integer expectedNumber;
|
||||
SerializedArgument expectedValue;
|
||||
Boolean useInnerText;
|
||||
boolean isNot;
|
||||
Double timeout;
|
||||
}
|
||||
|
||||
class FrameExpectResult {
|
||||
boolean matches;
|
||||
SerializedValue received;
|
||||
List<String> log;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,17 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
return redirectedTo != null ? redirectedTo.finalRequest() : this;
|
||||
}
|
||||
|
||||
private RawHeaders getRawHeaders() {
|
||||
if (rawHeaders != null) {
|
||||
return rawHeaders;
|
||||
}
|
||||
JsonArray rawHeadersJson = withLogging("Request.allHeaders", () -> {
|
||||
JsonObject result = 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
withLogging("Route.abort", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("errorCode", errorCode);
|
||||
sendMessage("abort", params);
|
||||
sendMessageAsync("abort", params);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
sendMessage("continue", params);
|
||||
sendMessageAsync("continue", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,7 +128,7 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
params.add("headers", Serialization.toProtocol(headers));
|
||||
params.addProperty("isBase64", isBase64);
|
||||
params.addProperty("body", body);
|
||||
sendMessage("fulfill", params);
|
||||
sendMessageAsync("fulfill", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,15 +29,35 @@ 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;
|
||||
}
|
||||
|
||||
boolean isDone() {
|
||||
return times != null && times <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
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 +72,10 @@ 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)) {
|
||||
if (info.isDone()) {
|
||||
routes.remove(info);
|
||||
}
|
||||
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;
|
||||
@@ -32,25 +33,26 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
class Serialization {
|
||||
private static Gson gson;
|
||||
private static Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.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>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
|
||||
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
|
||||
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
|
||||
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
|
||||
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();;
|
||||
|
||||
static Gson gson() {
|
||||
if (gson == null) {
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
|
||||
.registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe())
|
||||
.registerTypeAdapter(Media.class, new MediaSerializer())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
|
||||
.registerTypeAdapter((new TypeToken<List<KeyboardModifier>>(){}).getType(), new KeyboardModifiersSerializer())
|
||||
.registerTypeAdapter(Optional.class, new OptionalSerializer())
|
||||
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
|
||||
.registerTypeHierarchyAdapter(Map.class, new StringMapSerializer())
|
||||
.registerTypeAdapter(Path.class, new PathSerializer()).create();
|
||||
}
|
||||
return gson;
|
||||
}
|
||||
|
||||
@@ -252,6 +254,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 +278,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 +298,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 +351,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,95 @@
|
||||
/*
|
||||
* 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);
|
||||
params.addProperty("skipCompress", false);
|
||||
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(StartChunkOptions options) {
|
||||
context.withLogging("Tracing.startChunk", () -> {
|
||||
startChunkImpl(options);
|
||||
});
|
||||
}
|
||||
|
||||
private void startChunkImpl(StartChunkOptions options) {
|
||||
if (options == null) {
|
||||
options = new StartChunkOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
context.sendMessage("tracingStartChunk", params);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
public interface Transport {
|
||||
void send(String message);
|
||||
String poll(Duration timeout);
|
||||
void send(JsonObject message);
|
||||
JsonObject poll(Duration timeout);
|
||||
void close() throws IOException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TransportLogger implements Transport {
|
||||
private final Transport transport;
|
||||
|
||||
TransportLogger(Transport transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(JsonObject message) {
|
||||
String messageString = gson().toJson(message);
|
||||
logWithTimestamp("SEND ► " + messageString);
|
||||
transport.send(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject poll(Duration timeout) {
|
||||
JsonObject message = transport.poll(timeout);
|
||||
if (message != null) {
|
||||
String messageString = gson().toJson(message);
|
||||
logWithTimestamp("◀ RECV " + messageString);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
transport.close();
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
class WebSocketTransport implements Transport {
|
||||
private final BlockingQueue<String> incoming = new ArrayBlockingQueue<>(1000);
|
||||
private final ClientConnection clientConnection;
|
||||
private boolean isClosed;
|
||||
private volatile Exception lastError;
|
||||
ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
|
||||
private enum EventType { CLOSE }
|
||||
|
||||
private class ClientConnection extends WebSocketClient {
|
||||
ClientConnection(URI serverUri) {
|
||||
super(serverUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake handshakedata) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
incoming.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason, boolean remote) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception ex) {
|
||||
lastError = ex;
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketTransport(URI uri, Duration timeout) {
|
||||
clientConnection = new ClientConnection(uri);
|
||||
try {
|
||||
if (!clientConnection.connectBlocking(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
|
||||
throw new PlaywrightException("Failed to connect", lastError);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to connect", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
checkIfClosed();
|
||||
clientConnection.send(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String poll(Duration timeout) {
|
||||
checkIfClosed();
|
||||
try {
|
||||
return incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new PlaywrightException("Failed to read message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
isClosed = true;
|
||||
clientConnection.close();
|
||||
}
|
||||
|
||||
void onClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
void offClose(Consumer<WebSocketTransport> handler) {
|
||||
listeners.remove(EventType.CLOSE, handler);
|
||||
}
|
||||
|
||||
private void checkIfClosed() {
|
||||
if (isClosed) {
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
if (clientConnection.isClosed()) {
|
||||
isClosed = true;
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
throw new PlaywrightException("Playwright connection closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user