Compare commits
228 Commits
v1.12.1
...
release-1.26
| Author | SHA1 | Date | |
|---|---|---|---|
| c1c538c7dc | |||
| bfc7645d20 | |||
| c34a35d6a7 | |||
| 6ef0cc29dd | |||
| 8dfe959cce | |||
| 5979077403 | |||
| 020618d83d | |||
| 6bef6bb4a7 | |||
| 734d56f5d5 | |||
| 6e66861db3 | |||
| cf73a8d525 | |||
| 4a0ff6ef5c | |||
| 398c979378 | |||
| 39be690062 | |||
| b5b0a983bd | |||
| 6b842e0d50 | |||
| 321673407a | |||
| 4d278c391e | |||
| 5c47cfb1d5 | |||
| f036b3d38a | |||
| 385131e51b | |||
| 6cb9f60988 | |||
| aef9badd64 | |||
| 64f7a059af | |||
| 436fc12609 | |||
| 560575a9b5 | |||
| 0afaf6c561 | |||
| 202371b5d7 | |||
| 2093bba554 | |||
| 77538dcf7f | |||
| f759222755 | |||
| d87c6b24ca | |||
| 41355bd059 | |||
| e372513fa4 | |||
| b8e1e1d935 | |||
| a0745735d9 | |||
| b90de26d23 | |||
| adfdf92eaa | |||
| 60cb6ea7b3 | |||
| 44cb76d92c | |||
| 9fac877892 | |||
| ec8fb9f191 | |||
| 844d1665b2 | |||
| 484a255ec7 | |||
| 3f60144e0f | |||
| 8004e5d0ff | |||
| 3604aab710 | |||
| 2fdb89c94e | |||
| 4fee61a655 | |||
| efb281e016 | |||
| fdec32c650 | |||
| 7e285ffe44 | |||
| edf0e45fb4 | |||
| c8eb4f9eeb | |||
| e4ec9b8dbe | |||
| ef13ab86b8 | |||
| a48fef6b01 | |||
| 1c1f3d43ac | |||
| 8f59cd73f5 | |||
| e04ef2132c | |||
| 34d23a833e | |||
| 15eefc54af | |||
| abf245ccc7 | |||
| 10592ce5c7 | |||
| ef7f50c48a | |||
| 473b1ce794 | |||
| 98ecb7e0a0 | |||
| 04eb228813 | |||
| 298e01ee80 | |||
| b6b54af13c | |||
| b8d2ccae08 | |||
| 59e7c0cc94 | |||
| 54d0366b9e | |||
| 9845a05544 | |||
| 41fd9a6f75 | |||
| 483cf0d473 | |||
| 1681c410dd | |||
| 9f6860539a | |||
| 536af6b3d8 | |||
| 8ce193d144 | |||
| 7eddd2d2b2 | |||
| 447578c582 | |||
| 58013adfac | |||
| 43d12a7662 | |||
| 1deccbb55d | |||
| 1b2d33402e | |||
| d315e7b5bf | |||
| 7dc22aa08a | |||
| 5b0ef8b7bf | |||
| 43ba37817b | |||
| 4916ba22af | |||
| 94f72694f1 | |||
| be167a161b | |||
| 187d2ad6c6 | |||
| 68a7dbc1e3 | |||
| 1153d473fb | |||
| 00d53bd1ea | |||
| d423733d9d | |||
| e127abb68e | |||
| 5ee8f23380 | |||
| b448a1789a | |||
| 79b2c70513 | |||
| d476d0a98c | |||
| 2122c5690a | |||
| 3119102b10 | |||
| 838e7a40b3 | |||
| ea6ede4670 | |||
| 3cdefc2931 | |||
| 119700a678 | |||
| 17a4143a83 | |||
| c03f4a9384 | |||
| 85b671328e | |||
| 11f898ca7f | |||
| 2aef5c6742 | |||
| 897d441c02 | |||
| f4c69faad3 | |||
| 6b30c0b3d2 | |||
| a006d51872 | |||
| f411bf4194 | |||
| 25a0927056 | |||
| e9b379f5ed | |||
| 0c1d491c14 | |||
| b09b9aecfb | |||
| e926c1ae82 | |||
| 86e91590cb | |||
| 963afac983 | |||
| c230bed27e | |||
| a4348f250f | |||
| 52d31a173e | |||
| cf534a0586 | |||
| fa3bdebcbb | |||
| 0467aa7d4a | |||
| 8d84afccec | |||
| 7d1026ea9c | |||
| 0076b8f8a9 | |||
| 4d379726e5 | |||
| b4100a4d68 | |||
| 3b4d8dc955 | |||
| d71795801c | |||
| 16c7b7f25e | |||
| f739ae28e8 | |||
| 9f8ff0e7a1 | |||
| f782ca6339 | |||
| 90ccaa195f | |||
| 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 |
+4
-2
@@ -1,3 +1,5 @@
|
||||
# text files must be lf for golden file tests to work
|
||||
*.txt eol=lf
|
||||
*.json eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
# make project show as TS on GitHub
|
||||
*.js linguist-detectable=false
|
||||
|
||||
@@ -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,17 @@
|
||||
name: Publish
|
||||
on:
|
||||
workflow_dispatch
|
||||
release:
|
||||
types: [published]
|
||||
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:
|
||||
|
||||
@@ -3,7 +3,7 @@ name: "devrelease:docker"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
publish-canary-docker:
|
||||
name: "publish to DockerHub"
|
||||
@@ -17,10 +17,9 @@ jobs:
|
||||
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 }}
|
||||
- name: Set up Docker QEMU for arm64 docker builds
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64
|
||||
- name: publish docker canary
|
||||
run: ./utils/docker/publish_docker.sh canary
|
||||
|
||||
@@ -3,6 +3,11 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
is_release:
|
||||
required: true
|
||||
type: boolean
|
||||
description: "Is this a release image?"
|
||||
branches:
|
||||
- release-*
|
||||
jobs:
|
||||
@@ -17,20 +22,12 @@ jobs:
|
||||
login-server: playwright.azurecr.io
|
||||
username: playwright
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Set up Docker QEMU for arm64 docker builds
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64
|
||||
- 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
|
||||
- run: ./utils/docker/publish_docker.sh stable
|
||||
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
|
||||
- run: ./utils/docker/publish_docker.sh canary
|
||||
if: (github.event_name != 'workflow_dispatch' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release != 'true')
|
||||
|
||||
+52
-25
@@ -2,11 +2,11 @@ name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
jobs:
|
||||
dev:
|
||||
@@ -21,30 +21,29 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
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
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
- 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: Build & Install
|
||||
run: mvn -B install -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: Run tracing tests w/ sources
|
||||
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
PLAYWRIGHT_JAVA_SRC: src/test/java
|
||||
- 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
|
||||
@@ -70,22 +69,50 @@ jobs:
|
||||
shell: powershell
|
||||
run: Install-WindowsFeature Server-Media-Foundation
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
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
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
- 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: Build & Install
|
||||
run: mvn -B install -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: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
|
||||
Java_17:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: adopt
|
||||
java-version: 17
|
||||
- name: Download drivers
|
||||
shell: bash
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
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: |
|
||||
cd tools/test-spring-boot-starter
|
||||
mvn package -D skipTests --no-transfer-progress
|
||||
java -jar target/test-spring-boot*.jar
|
||||
|
||||
@@ -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
|
||||
@@ -28,3 +26,6 @@ jobs:
|
||||
run: mvn install -D skipTests --no-transfer-progress
|
||||
- name: Test CLI
|
||||
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
|
||||
- name: Test CLI version
|
||||
shell: bash
|
||||
run: tools/test-cli-version/test.sh
|
||||
|
||||
@@ -3,18 +3,18 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/test_docker.yml'
|
||||
- 'Dockerfile*'
|
||||
- '**/Dockerfile*'
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/test_docker.yml
|
||||
- Dockerfile.*
|
||||
- '**/Dockerfile*'
|
||||
- scripts/CLI_VERSION
|
||||
- '**/pom.xml'
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
jobs:
|
||||
test:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Docker image
|
||||
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
|
||||
run: bash utils/docker/build.sh --amd64 focal playwright-java:localbuild-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)"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "Internal Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: "trigger"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: |
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${GH_TOKEN}" \
|
||||
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
|
||||
https://api.github.com/repos/microsoft/playwright-internal/dispatches
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
|
||||
@@ -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,25 +17,20 @@ on:
|
||||
jobs:
|
||||
verify:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Download drivers
|
||||
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
|
||||
|
||||
+18
-1
@@ -8,7 +8,7 @@ Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubun
|
||||
just run the following command:
|
||||
|
||||
```sh
|
||||
sudo apt-get install git openjdk-11-jdk maven
|
||||
sudo apt-get install git openjdk-11-jdk maven unzip
|
||||
```
|
||||
|
||||
### Getting the Code
|
||||
@@ -33,6 +33,10 @@ Names of published driver archives can be found at https://github.com/microsoft/
|
||||
```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
|
||||
@@ -45,6 +49,19 @@ Java interfaces for the current driver run the following commands:
|
||||
./scripts/generate_api.sh
|
||||
```
|
||||
|
||||
#### Updating driver version
|
||||
|
||||
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
|
||||
|
||||
```bash
|
||||
cat > scripts/CLI_VERSION
|
||||
<paste new version>
|
||||
^D
|
||||
./scripts/download_driver_for_all_platforms.sh -f
|
||||
./scripts/generate_api.sh
|
||||
./scripts/update_readme.sh
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- We try to follow [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
FROM ubuntu:focal
|
||||
|
||||
# === INSTALL JDK and Maven ===
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
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 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 && \
|
||||
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
|
||||
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->92.0.4498.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->14.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->89.0b6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->106.0.5249.30<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->104.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
|
||||
|
||||
@@ -43,10 +43,18 @@ To run Playwright simply add following dependency to your Maven project:
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.11.0</version>
|
||||
<version>1.17.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
To run Playwright using Gradle add following dependency to your build.gradle file:
|
||||
|
||||
```json lines
|
||||
dependencies {
|
||||
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.25.0'
|
||||
}
|
||||
```
|
||||
|
||||
#### 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 multiple Playwright instances each on its own thread.
|
||||
@@ -173,13 +181,13 @@ public class InterceptNetworkRequests {
|
||||
|
||||
## Documentation
|
||||
|
||||
Check out our [new documentation site](https://playwright.dev/java)!.
|
||||
Check out our official [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?
|
||||
|
||||
|
||||
+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
|
||||
```
|
||||
+2
-17
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.12.0-SNAPSHOT</version>
|
||||
<version>1.26.1</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,25 +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>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
+77
-16
@@ -14,32 +14,80 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
package com.microsoft.playwright.impl.driver.jar;
|
||||
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
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 static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
|
||||
static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
|
||||
private final Path driverTempDir;
|
||||
private Path preinstalledNodePath;
|
||||
|
||||
DriverJar() throws IOException, URISyntaxException, InterruptedException {
|
||||
driverTempDir = Files.createTempDirectory("playwright-java-");
|
||||
public DriverJar() throws IOException {
|
||||
// Allow specifying custom path for the driver installation
|
||||
// See https://github.com/microsoft/playwright-java/issues/728
|
||||
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
|
||||
String prefix = "playwright-java-";
|
||||
driverTempDir = alternativeTmpdir == null
|
||||
? Files.createTempDirectory(prefix)
|
||||
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
extractDriverToTempDir();
|
||||
installBrowsers();
|
||||
String nodePath = System.getProperty("playwright.nodejs.path");
|
||||
if (nodePath != null) {
|
||||
preinstalledNodePath = Paths.get(nodePath);
|
||||
if (!Files.exists(preinstalledNodePath)) {
|
||||
throw new RuntimeException("Invalid Node.js path specified: " + nodePath);
|
||||
}
|
||||
}
|
||||
logMessage("created DriverJar: " + driverTempDir);
|
||||
}
|
||||
|
||||
private void installBrowsers() throws IOException, InterruptedException {
|
||||
String cliFileName = super.cliFileName();
|
||||
Path driver = driverTempDir.resolve(cliFileName);
|
||||
if (!Files.exists(driver)) {
|
||||
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
|
||||
@Override
|
||||
protected void initialize(Boolean installBrowsers) throws Exception {
|
||||
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
|
||||
preinstalledNodePath = Paths.get(env.get(PLAYWRIGHT_NODEJS_PATH));
|
||||
if (!Files.exists(preinstalledNodePath)) {
|
||||
throw new RuntimeException("Invalid Node.js path specified: " + preinstalledNodePath);
|
||||
}
|
||||
} else if (preinstalledNodePath != null) {
|
||||
// Pass the env variable to the driver process.
|
||||
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
|
||||
}
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
|
||||
extractDriverToTempDir();
|
||||
logMessage("extracted driver from jar to " + driverPath());
|
||||
if (installBrowsers)
|
||||
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;
|
||||
}
|
||||
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
|
||||
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
|
||||
return;
|
||||
}
|
||||
Path driver = driverPath();
|
||||
if (!Files.exists(driver)) {
|
||||
throw new RuntimeException("Failed to find driver: " + driver);
|
||||
}
|
||||
ProcessBuilder pb = createProcessBuilder();
|
||||
pb.command().add("install");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
Process p = pb.start();
|
||||
@@ -58,9 +106,10 @@ public class DriverJar extends Driver {
|
||||
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
|
||||
}
|
||||
|
||||
private void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
||||
URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
|
||||
URI originalUri = classloader.getResource(
|
||||
"driver/" + platformDir()).toURI();
|
||||
URI uri = maybeExtractNestedJar(originalUri);
|
||||
|
||||
// Create zip filesystem if loading from jar.
|
||||
@@ -72,6 +121,12 @@ public class DriverJar extends Driver {
|
||||
// See https://github.com/microsoft/playwright-java/issues/306
|
||||
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
|
||||
Files.walk(srcRoot).forEach(fromPath -> {
|
||||
if (preinstalledNodePath != null) {
|
||||
String fileName = fromPath.getFileName().toString();
|
||||
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
|
||||
Path toPath = driverTempDir.resolve(relative.toString());
|
||||
try {
|
||||
@@ -115,11 +170,17 @@ public class DriverJar extends Driver {
|
||||
|
||||
private static String platformDir() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch").toLowerCase();
|
||||
|
||||
if (name.contains("windows")) {
|
||||
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
|
||||
return "win32_x64";
|
||||
}
|
||||
if (name.contains("linux")) {
|
||||
return "linux";
|
||||
if (arch.equals("aarch64")) {
|
||||
return "linux-arm64";
|
||||
} else {
|
||||
return "linux";
|
||||
}
|
||||
}
|
||||
if (name.contains("mac os x")) {
|
||||
return "mac";
|
||||
@@ -128,7 +189,7 @@ public class DriverJar extends Driver {
|
||||
}
|
||||
|
||||
@Override
|
||||
Path driverDir() {
|
||||
protected Path driverDir() {
|
||||
return driverTempDir;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestInstall {
|
||||
@Test
|
||||
void playwrightCliInstalled() throws Exception {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
Path cli = Driver.ensureDriverInstalled();
|
||||
assertTrue(Files.exists(cli));
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl.driver.jar;
|
||||
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.microsoft.playwright.impl.driver.jar.DriverJar.PLAYWRIGHT_NODEJS_PATH;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestInstall {
|
||||
private static boolean isPortAvailable(int port) {
|
||||
try (ServerSocket ignored = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int unusedPort() {
|
||||
for (int i = 10000; i < 11000; i++) {
|
||||
if (isPortAvailable(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Cannot find unused local port");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearSystemProperties() {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
System.clearProperty("playwright.driver.tmpdir");
|
||||
System.clearProperty("playwright.nodejs.path");
|
||||
// Clear system property to ensure that the default driver is loaded.
|
||||
System.clearProperty("playwright.driver.impl");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) throws NoSuchFieldException, IllegalAccessException {
|
||||
Map<String,String> env = new HashMap<>();
|
||||
|
||||
// On macOS we can only use 127.0.0.1, so pick unused port instead.
|
||||
// https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x
|
||||
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.1:" + unusedPort());
|
||||
// Make sure the browsers are not installed yet by pointing at an empty dir.
|
||||
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
|
||||
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.createAndInstall(env, true));
|
||||
String message = exception.getMessage();
|
||||
assertTrue(message.contains("Failed to create driver"), message);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightCliInstalled() throws Exception {
|
||||
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
|
||||
assertTrue(Files.exists(driver.driverPath()));
|
||||
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
|
||||
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
|
||||
DriverJar driver = new DriverJar();
|
||||
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverDefaultImpl() {
|
||||
assertDoesNotThrow(() -> Driver.createAndInstall(Collections.emptyMap(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
|
||||
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
|
||||
RuntimeException thrown =
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> Driver.createAndInstall(Collections.emptyMap(), false));
|
||||
assertEquals("Failed to create driver", thrown.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void canPassPreinstalledNodeJsAsSystemProperty(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
String nodePath = extractNodeJsToTemp();
|
||||
System.setProperty("playwright.nodejs.path", nodePath);
|
||||
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
|
||||
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
String nodePath = extractNodeJsToTemp();
|
||||
Driver driver = Driver.createAndInstall(singletonMap(PLAYWRIGHT_NODEJS_PATH, nodePath), false);
|
||||
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
|
||||
}
|
||||
|
||||
|
||||
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
|
||||
DriverJar auxDriver = new DriverJar();
|
||||
auxDriver.extractDriverToTempDir();
|
||||
String nodePath = auxDriver.driverPath().getParent().resolve(isWindows() ? "node.exe" : "node").toString();
|
||||
return nodePath;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
return name.contains("win");
|
||||
}
|
||||
|
||||
private static void canSpecifyPreinstalledNodeJsShared(Driver driver, Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
|
||||
Path builtinNode = driver.driverPath().getParent().resolve("node");
|
||||
assertFalse(Files.exists(builtinNode), builtinNode.toString());
|
||||
Path builtinNodeExe = driver.driverPath().getParent().resolve("node.exe");
|
||||
assertFalse(Files.exists(builtinNodeExe), builtinNodeExe.toString());
|
||||
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("--version");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
Path out = tmpDir.resolve("out.txt");
|
||||
pb.redirectOutput(out.toFile());
|
||||
Process p = pb.start();
|
||||
boolean result = p.waitFor(1, TimeUnit.MINUTES);
|
||||
assertTrue(result, "Timed out waiting for version to be printed");
|
||||
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
|
||||
assertTrue(stdout.contains("Version "), stdout);
|
||||
}
|
||||
}
|
||||
+2
-17
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.12.0-SNAPSHOT</version>
|
||||
<version>1.26.1</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,25 +24,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>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -1,69 +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 java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* This class provides access to playwright-cli. It can be either preinstalled
|
||||
* in the host system and its path is passed as a system property or it can be
|
||||
* loaded from the driver-bundle module if that module is in the classpath.
|
||||
*/
|
||||
public abstract class Driver {
|
||||
private static Driver instance;
|
||||
|
||||
private static class PreinstalledDriver extends Driver {
|
||||
private final Path driverDir;
|
||||
PreinstalledDriver(Path driverDir) {
|
||||
this.driverDir = driverDir;
|
||||
}
|
||||
@Override
|
||||
Path driverDir() {
|
||||
return driverDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Path ensureDriverInstalled() {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = createDriver();
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
String name = instance.cliFileName();
|
||||
return instance.driverDir().resolve(name);
|
||||
}
|
||||
|
||||
protected String cliFileName() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
"playwright.cmd" : "playwright.sh";
|
||||
}
|
||||
|
||||
private static Driver createDriver() throws Exception {
|
||||
String pathFromProperty = System.getProperty("playwright.cli.dir");
|
||||
if (pathFromProperty != null) {
|
||||
return new PreinstalledDriver(Paths.get(pathFromProperty));
|
||||
}
|
||||
|
||||
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
|
||||
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
abstract Path driverDir();
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.driver;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
|
||||
|
||||
/**
|
||||
* This class provides access to playwright-cli. It can be either preinstalled
|
||||
* in the host system and its path is passed as a system property or it can be
|
||||
* loaded from the driver-bundle module if that module is in the classpath.
|
||||
*/
|
||||
public abstract class Driver {
|
||||
protected final Map<String, String> env = new LinkedHashMap<>();
|
||||
|
||||
private static Driver instance;
|
||||
|
||||
private static class PreinstalledDriver extends Driver {
|
||||
private final Path driverDir;
|
||||
PreinstalledDriver(Path driverDir) {
|
||||
logMessage("created PreinstalledDriver: " + driverDir);
|
||||
this.driverDir = driverDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Boolean installBrowsers) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path driverDir() {
|
||||
return driverDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
|
||||
if (instance == null) {
|
||||
instance = createAndInstall(env, installBrowsers);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
this.env.putAll(env);
|
||||
initialize(installBrowsers);
|
||||
}
|
||||
protected abstract void initialize(Boolean installBrowsers) throws Exception;
|
||||
|
||||
public Path driverPath() {
|
||||
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
|
||||
"playwright.cmd" : "playwright.sh";
|
||||
return driverDir().resolve(cliFileName);
|
||||
}
|
||||
|
||||
public ProcessBuilder createProcessBuilder() {
|
||||
ProcessBuilder pb = new ProcessBuilder(driverPath().toString());
|
||||
pb.environment().putAll(env);
|
||||
pb.environment().put("PW_LANG_NAME", "java");
|
||||
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
|
||||
String version = Driver.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
}
|
||||
return pb;
|
||||
}
|
||||
|
||||
private static String getMajorJavaVersion() {
|
||||
String version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.")) {
|
||||
return version.substring(2, 3);
|
||||
}
|
||||
int dot = version.indexOf(".");
|
||||
if (dot != -1) {
|
||||
return version.substring(0, dot);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
public static Driver createAndInstall(Map<String, String> env, Boolean installBrowsers) {
|
||||
try {
|
||||
Driver instance = newInstance();
|
||||
logMessage("initializing driver");
|
||||
instance.initialize(env, installBrowsers);
|
||||
logMessage("driver initialized.");
|
||||
return instance;
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static Driver newInstance() throws Exception {
|
||||
String pathFromProperty = System.getProperty("playwright.cli.dir");
|
||||
if (pathFromProperty != null) {
|
||||
return new PreinstalledDriver(Paths.get(pathFromProperty));
|
||||
}
|
||||
|
||||
String driverImpl =
|
||||
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
|
||||
Class<?> jarDriver = Class.forName(driverImpl);
|
||||
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
protected abstract Path driverDir();
|
||||
|
||||
protected static void logMessage(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:install " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl.driver;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
class DriverLogging {
|
||||
private static final boolean isEnabled;
|
||||
static {
|
||||
String debug = System.getenv("DEBUG");
|
||||
isEnabled = (debug != null) && debug.contains("pw:install");
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
|
||||
|
||||
static void logWithTimestamp(String message) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
// This matches log format produced by the server.
|
||||
String timestamp = ZonedDateTime.now().format(timestampFormat);
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.12.0-SNAPSHOT</version>
|
||||
<version>1.26.1</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -15,7 +15,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>playwright</artifactId>
|
||||
<version>1.11.1</version>
|
||||
<version>1.22.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.example;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class SelectorsAndKeyboardManipulation {
|
||||
public static void main(String[] args) {
|
||||
try(Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://playwright.dev/java/");
|
||||
page.locator("text=SearchK").click();
|
||||
page.locator("[placeholder=\"Search docs\"]").fill("getting started");
|
||||
page.locator("div[role=\"button\"]:has-text(\"CancelIntroductionGetting startedInstallationGetting startedUsageGetting start\")").click();
|
||||
page.waitForSelector("h1:has-text(\"Getting started\")"); // Waits for the new page to load before screenshotting.
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("Screenshot.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-11
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.12.0-SNAPSHOT</version>
|
||||
<version>1.26.1</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>
|
||||
@@ -51,7 +41,24 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<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>
|
||||
@@ -66,6 +73,10 @@
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.opentest4j</groupId>
|
||||
<artifactId>opentest4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance which
|
||||
* in turn can be used for sending web requests. An instance of this class can be obtained via {@link Playwright#request
|
||||
* Playwright.request()}. For more information see {@code APIRequestContext}.
|
||||
*/
|
||||
public interface APIRequest {
|
||||
class NewContextOptions {
|
||||
/**
|
||||
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into 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 sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request.
|
||||
*/
|
||||
public Map<String, String> extraHTTPHeaders;
|
||||
/**
|
||||
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
|
||||
*/
|
||||
public HttpCredentials httpCredentials;
|
||||
/**
|
||||
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean ignoreHTTPSErrors;
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public Proxy proxy;
|
||||
/**
|
||||
* 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()} or {@link APIRequestContext#storageState
|
||||
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
|
||||
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
|
||||
* APIRequestContext.storageState()} methods.
|
||||
*/
|
||||
public String storageState;
|
||||
/**
|
||||
* 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 Path storageStatePath;
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
|
||||
*/
|
||||
public Double timeout;
|
||||
/**
|
||||
* Specific user agent to use in this context.
|
||||
*/
|
||||
public String userAgent;
|
||||
|
||||
/**
|
||||
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into 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 sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/foo/bar.html}</li>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewContextOptions setBaseURL(String baseURL) {
|
||||
this.baseURL = baseURL;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public NewContextOptions setProxy(String server) {
|
||||
return setProxy(new Proxy(server));
|
||||
}
|
||||
/**
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public NewContextOptions setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
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()} or {@link APIRequestContext#storageState
|
||||
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
|
||||
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
|
||||
* APIRequestContext.storageState()} methods.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
|
||||
*/
|
||||
public NewContextOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Specific user agent to use in this context.
|
||||
*/
|
||||
public NewContextOptions setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates new instances of {@code APIRequestContext}.
|
||||
*/
|
||||
default APIRequestContext newContext() {
|
||||
return newContext(null);
|
||||
}
|
||||
/**
|
||||
* Creates new instances of {@code APIRequestContext}.
|
||||
*/
|
||||
APIRequestContext newContext(NewContextOptions options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
|
||||
* environment or the service to your e2e test.
|
||||
*
|
||||
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage with the
|
||||
* browser context and can be accessed via {@link BrowserContext#request BrowserContext.request()} or {@link Page#request
|
||||
* Page.request()}. It is also possible to create a new APIRequestContext instance manually by calling {@link
|
||||
* APIRequest#newContext APIRequest.newContext()}.
|
||||
*
|
||||
* <p> **Cookie management**
|
||||
*
|
||||
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
|
||||
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
|
||||
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
|
||||
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
|
||||
* log in using this API, your e2e test will be logged in and vice versa.
|
||||
*
|
||||
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
|
||||
* calling {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own
|
||||
* isolated cookie storage.
|
||||
*/
|
||||
public interface APIRequestContext {
|
||||
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
|
||||
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
|
||||
* its response. The method will populate request cookies from the context and update context cookies from the response.
|
||||
* The method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse delete(String url) {
|
||||
return delete(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
|
||||
* its response. The method will populate request cookies from the context and update context cookies from the response.
|
||||
* The method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse delete(String url, RequestOptions params);
|
||||
/**
|
||||
* All responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar methods are stored in the
|
||||
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}. This method discards all stored
|
||||
* responses, and makes {@link APIResponse#body APIResponse.body()} throw "Response disposed" error.
|
||||
*/
|
||||
void dispose();
|
||||
/**
|
||||
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
|
||||
* context cookies from the response. The method will automatically follow redirects.
|
||||
*
|
||||
* @param urlOrRequest Target URL or Request to get all parameters from.
|
||||
*/
|
||||
default APIResponse fetch(String urlOrRequest) {
|
||||
return fetch(urlOrRequest, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
|
||||
* context cookies from the response. The method will automatically follow redirects.
|
||||
*
|
||||
* @param urlOrRequest Target URL or Request to get all parameters from.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse fetch(String urlOrRequest, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
|
||||
* context cookies from the response. The method will automatically follow redirects.
|
||||
*
|
||||
* @param urlOrRequest Target URL or Request to get all parameters from.
|
||||
*/
|
||||
default APIResponse fetch(Request urlOrRequest) {
|
||||
return fetch(urlOrRequest, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
|
||||
* context cookies from the response. The method will automatically follow redirects.
|
||||
*
|
||||
* @param urlOrRequest Target URL or Request to get all parameters from.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse fetch(Request urlOrRequest, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse get(String url) {
|
||||
return get(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse get(String url, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse head(String url) {
|
||||
return head(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse head(String url, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
|
||||
* its response. The method will populate request cookies from the context and update context cookies from the response.
|
||||
* The method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse patch(String url) {
|
||||
return patch(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
|
||||
* its response. The method will populate request cookies from the context and update context cookies from the response.
|
||||
* The method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse patch(String url, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse post(String url) {
|
||||
return post(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse post(String url, RequestOptions params);
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
*/
|
||||
default APIResponse put(String url) {
|
||||
return put(url, null);
|
||||
}
|
||||
/**
|
||||
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
|
||||
* response. The method will populate request cookies from the context and update context cookies from the response. The
|
||||
* method will automatically follow redirects.
|
||||
*
|
||||
* @param url Target URL.
|
||||
* @param params Optional request parameters.
|
||||
*/
|
||||
APIResponse put(String url, RequestOptions params);
|
||||
/**
|
||||
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
|
||||
* the constructor.
|
||||
*/
|
||||
default String storageState() {
|
||||
return storageState(null);
|
||||
}
|
||||
/**
|
||||
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
|
||||
* the constructor.
|
||||
*/
|
||||
String storageState(StorageStateOptions options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code APIResponse} class represents responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar
|
||||
* methods.
|
||||
*/
|
||||
public interface APIResponse {
|
||||
/**
|
||||
* Returns the buffer with response body.
|
||||
*/
|
||||
byte[] body();
|
||||
/**
|
||||
* Disposes the body of this response. If not called then the body will stay in memory until the context closes.
|
||||
*/
|
||||
void dispose();
|
||||
/**
|
||||
* An object with all the response HTTP headers associated with this response.
|
||||
*/
|
||||
Map<String, String> headers();
|
||||
/**
|
||||
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
|
||||
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
|
||||
*/
|
||||
List<HttpHeader> headersArray();
|
||||
/**
|
||||
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
|
||||
*/
|
||||
boolean ok();
|
||||
/**
|
||||
* Contains the status code of the response (e.g., 200 for a success).
|
||||
*/
|
||||
int status();
|
||||
/**
|
||||
* Contains the status text of the response (e.g. usually an "OK" for a success).
|
||||
*/
|
||||
String statusText();
|
||||
/**
|
||||
* Returns the text representation of response body.
|
||||
*/
|
||||
String text();
|
||||
/**
|
||||
* Contains the URL of the response.
|
||||
*/
|
||||
String url();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A Browser is created via {@link BrowserType#launch BrowserType.launch()}. An example of using a {@code Browser} to create a
|
||||
@@ -57,9 +58,23 @@ public interface Browser extends AutoCloseable {
|
||||
|
||||
class NewContextOptions {
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
@@ -74,9 +89,14 @@ 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"}.
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
@@ -121,6 +141,17 @@ public interface Browser extends AutoCloseable {
|
||||
* 'http://per-context' } })}.
|
||||
*/
|
||||
public Proxy proxy;
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public HarContentPolicy recordHarContent;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -131,6 +162,7 @@ public interface Browser extends AutoCloseable {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* 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.
|
||||
@@ -152,6 +184,15 @@ public interface Browser extends AutoCloseable {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* 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()}.
|
||||
@@ -163,6 +204,12 @@ public interface Browser extends AutoCloseable {
|
||||
* state.
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* If 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
|
||||
@@ -178,26 +225,67 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/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"}.
|
||||
*/
|
||||
public NewContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -205,97 +293,244 @@ 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 resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public NewContextOptions setRecordHarContent(HarContentPolicy recordHarContent) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public NewContextOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
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;
|
||||
}
|
||||
public NewContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public NewContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* If 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;
|
||||
@@ -303,9 +538,23 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
class NewPageOptions {
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
@@ -320,9 +569,14 @@ 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"}.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
/**
|
||||
* Specifies if viewport supports touch events. Defaults to false.
|
||||
@@ -333,7 +587,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;
|
||||
/**
|
||||
@@ -367,6 +621,17 @@ public interface Browser extends AutoCloseable {
|
||||
* 'http://per-context' } })}.
|
||||
*/
|
||||
public Proxy proxy;
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public HarContentPolicy recordHarContent;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -377,6 +642,7 @@ public interface Browser extends AutoCloseable {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* 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.
|
||||
@@ -398,6 +664,15 @@ public interface Browser extends AutoCloseable {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* 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()}.
|
||||
@@ -409,6 +684,12 @@ public interface Browser extends AutoCloseable {
|
||||
* state.
|
||||
*/
|
||||
public Path storageStatePath;
|
||||
/**
|
||||
* If 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
|
||||
@@ -424,26 +705,67 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/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"}.
|
||||
*/
|
||||
public NewPageOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -451,97 +773,244 @@ 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 resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public NewPageOptions setRecordHarContent(HarContentPolicy recordHarContent) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public NewPageOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
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;
|
||||
}
|
||||
public NewPageOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public NewPageOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public NewPageOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* If 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;
|
||||
@@ -561,19 +1030,32 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the browser type (chromium, firefox or webkit) that the browser belongs to.
|
||||
*/
|
||||
BrowserType browserType();
|
||||
/**
|
||||
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
|
||||
* its pages (if any were opened).
|
||||
@@ -581,6 +1063,10 @@ public interface Browser extends AutoCloseable {
|
||||
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
|
||||
* browser server.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link BrowserContext#close
|
||||
* BrowserContext.close()} on any {@code BrowserContext}'s you explicitly created earlier with {@link Browser#newContext
|
||||
* Browser.newContext()} **before** calling {@link Browser#close Browser.close()}.
|
||||
*
|
||||
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
|
||||
*/
|
||||
void close();
|
||||
@@ -600,6 +1086,11 @@ public interface Browser extends AutoCloseable {
|
||||
boolean isConnected();
|
||||
/**
|
||||
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
|
||||
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
|
||||
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
|
||||
* artifacts—like HARs and videos—are fully flushed and saved.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
|
||||
* // Create a new incognito browser context.
|
||||
@@ -607,6 +1098,10 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Graceful close up everything
|
||||
* context.close();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*/
|
||||
default BrowserContext newContext() {
|
||||
@@ -614,6 +1109,11 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* Creates a new browser context. It won't share cookies/cache with other browser contexts.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> If directly using this method to create {@code BrowserContext}s, it is best practice to explicitly close the returned context
|
||||
* via {@link BrowserContext#close BrowserContext.close()} when your code is done with the {@code BrowserContext}, and before
|
||||
* calling {@link Browser#close Browser.close()}. This will ensure the {@code context} is closed gracefully and any
|
||||
* artifacts—like HARs and videos—are fully flushed and saved.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
|
||||
* // Create a new incognito browser context.
|
||||
@@ -621,6 +1121,10 @@ public interface Browser extends AutoCloseable {
|
||||
* // Create a new page in a pristine context.
|
||||
* Page page = context.newPage();
|
||||
* page.navigate('https://example.com');
|
||||
*
|
||||
* // Graceful close up everything
|
||||
* context.close();
|
||||
* browser.close();
|
||||
* }</pre>
|
||||
*/
|
||||
BrowserContext newContext(NewContextOptions options);
|
||||
@@ -643,7 +1147,10 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
Page newPage(NewPageOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
|
||||
* <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="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/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.
|
||||
@@ -660,7 +1167,10 @@ public interface Browser extends AutoCloseable {
|
||||
startTracing(page, null);
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
|
||||
* <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="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/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.
|
||||
@@ -675,7 +1185,10 @@ public interface Browser extends AutoCloseable {
|
||||
startTracing(null);
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
|
||||
* <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="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/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.
|
||||
@@ -690,7 +1203,10 @@ public interface Browser extends AutoCloseable {
|
||||
*/
|
||||
void startTracing(Page page, StartTracingOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
|
||||
* <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="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
*
|
||||
* <p> Returns the buffer with trace data.
|
||||
*/
|
||||
|
||||
@@ -29,8 +29,8 @@ 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();
|
||||
@@ -67,7 +67,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* done and its response has started loading in the popup.
|
||||
* <pre>{@code
|
||||
* Page newPage = context.waitForPage(() -> {
|
||||
* page.click("a[target=_blank]");
|
||||
* page.locator("a[target=_blank]").click();
|
||||
* });
|
||||
* System.out.println(newPage.evaluate("location.href"));
|
||||
* }</pre>
|
||||
@@ -137,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;
|
||||
@@ -148,11 +152,84 @@ 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 RouteFromHAROptions {
|
||||
/**
|
||||
* <ul>
|
||||
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
|
||||
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to abort.
|
||||
*/
|
||||
public HarNotFound notFound;
|
||||
/**
|
||||
* If specified, updates the given HAR with the actual network information instead of serving from file.
|
||||
*/
|
||||
public Boolean update;
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public Object url;
|
||||
|
||||
/**
|
||||
* <ul>
|
||||
* <li> If set to 'abort' any request not found in the HAR file will be aborted.</li>
|
||||
* <li> If set to 'fallback' falls through to the next route handler in the handler chain.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> Defaults to abort.
|
||||
*/
|
||||
public RouteFromHAROptions setNotFound(HarNotFound notFound) {
|
||||
this.notFound = notFound;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If specified, updates the given HAR with the actual network information instead of serving from file.
|
||||
*/
|
||||
public RouteFromHAROptions setUpdate(boolean update) {
|
||||
this.update = update;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public RouteFromHAROptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
|
||||
* will be served from the HAR file. If not specified, all requests are served from the HAR file.
|
||||
*/
|
||||
public RouteFromHAROptions setUrl(Pattern url) {
|
||||
this.url = url;
|
||||
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
|
||||
@@ -160,6 +237,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;
|
||||
@@ -176,10 +257,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;
|
||||
@@ -316,7 +404,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -375,7 +463,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -445,7 +533,7 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>\n");
|
||||
* page.click("button");
|
||||
* page.locator("button").click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
@@ -465,7 +553,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <li> {@code "midi"}</li>
|
||||
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
|
||||
* <li> {@code "notifications"}</li>
|
||||
* <li> {@code "push"}</li>
|
||||
* <li> {@code "camera"}</li>
|
||||
* <li> {@code "microphone"}</li>
|
||||
* <li> {@code "background-sync"}</li>
|
||||
@@ -492,7 +579,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <li> {@code "midi"}</li>
|
||||
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
|
||||
* <li> {@code "notifications"}</li>
|
||||
* <li> {@code "push"}</li>
|
||||
* <li> {@code "camera"}</li>
|
||||
* <li> {@code "microphone"}</li>
|
||||
* <li> {@code "background-sync"}</li>
|
||||
@@ -516,53 +602,17 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
List<Page> pages();
|
||||
/**
|
||||
* 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 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.
|
||||
* @param handler handler function to route the request.
|
||||
* API testing helper associated with this context. Requests made with this API will use context cookies.
|
||||
*/
|
||||
void route(String url, Consumer<Route> handler);
|
||||
APIRequestContext request();
|
||||
/**
|
||||
* 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 BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
* BrowserContext context = browser.newContext();
|
||||
@@ -599,14 +649,22 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*
|
||||
* <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);
|
||||
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> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <p> An example of a naive handler that aborts all image requests:
|
||||
* <pre>{@code
|
||||
* BrowserContext context = browser.newContext();
|
||||
@@ -643,16 +701,248 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*
|
||||
* <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);
|
||||
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> <strong>NOTE:</strong> {@link BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <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(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 BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <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 BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <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 BrowserContext#route BrowserContext.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 by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* <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);
|
||||
/**
|
||||
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
|
||||
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
|
||||
*
|
||||
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
|
||||
* is a relative path, then it is resolved relative to the current working directory.
|
||||
*/
|
||||
default void routeFromHAR(Path har) {
|
||||
routeFromHAR(har, null);
|
||||
}
|
||||
/**
|
||||
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
|
||||
* href="https://playwright.dev/java/docs/network#replaying-from-har">Replaying from HAR</a>.
|
||||
*
|
||||
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
|
||||
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
|
||||
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
|
||||
*
|
||||
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code path}
|
||||
* is a relative path, then it is resolved relative to the current working directory.
|
||||
*/
|
||||
void routeFromHAR(Path har, RouteFromHAROptions 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>
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.microsoft.playwright;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* BrowserType provides methods to launch a specific browser instance or connect to an existing one. The following is a
|
||||
@@ -52,19 +53,28 @@ public interface BrowserType {
|
||||
*/
|
||||
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.
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
|
||||
*/
|
||||
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 0} (no timeout).
|
||||
*/
|
||||
public ConnectOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
@@ -86,14 +96,25 @@ public interface BrowserType {
|
||||
*/
|
||||
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;
|
||||
@@ -108,7 +129,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* 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>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public Object channel;
|
||||
/**
|
||||
@@ -122,7 +143,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;
|
||||
/**
|
||||
@@ -187,82 +209,159 @@ public interface BrowserType {
|
||||
*/
|
||||
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;
|
||||
@@ -270,7 +369,7 @@ public interface BrowserType {
|
||||
}
|
||||
class LaunchPersistentContextOptions {
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code false} where all the downloads are canceled.
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
public Boolean acceptDownloads;
|
||||
/**
|
||||
@@ -278,6 +377,20 @@ 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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/bar.html}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public String baseURL;
|
||||
/**
|
||||
* Toggles bypassing page's Content-Security-Policy.
|
||||
*/
|
||||
@@ -285,7 +398,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* 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>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public Object channel;
|
||||
/**
|
||||
@@ -308,7 +421,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;
|
||||
/**
|
||||
@@ -322,9 +436,14 @@ public interface BrowserType {
|
||||
*/
|
||||
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"}.
|
||||
*/
|
||||
public ForcedColors forcedColors;
|
||||
public Geolocation geolocation;
|
||||
/**
|
||||
* Close the browser process on SIGHUP. Defaults to {@code true}.
|
||||
@@ -364,7 +483,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;
|
||||
/**
|
||||
@@ -394,6 +513,17 @@ public interface BrowserType {
|
||||
* Network proxy settings.
|
||||
*/
|
||||
public Proxy proxy;
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public HarContentPolicy recordHarContent;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public HarMode recordHarMode;
|
||||
/**
|
||||
* Optional setting to control whether to omit request content from the HAR. Defaults to {@code false}.
|
||||
*/
|
||||
@@ -404,6 +534,7 @@ public interface BrowserType {
|
||||
* BrowserContext.close()} for the HAR to be saved.
|
||||
*/
|
||||
public Path recordHarPath;
|
||||
public Object recordHarUrlFilter;
|
||||
/**
|
||||
* 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.
|
||||
@@ -425,10 +556,25 @@ public interface BrowserType {
|
||||
* is set.
|
||||
*/
|
||||
public ScreenSize screenSize;
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public ServiceWorkerPolicy serviceWorkers;
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
*/
|
||||
public Double slowMo;
|
||||
/**
|
||||
* If 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.
|
||||
@@ -453,59 +599,134 @@ public interface BrowserType {
|
||||
*/
|
||||
public Optional<ViewportSize> viewportSize;
|
||||
|
||||
/**
|
||||
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
|
||||
*/
|
||||
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>
|
||||
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
|
||||
* {@code http://localhost:3000/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"}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
|
||||
this.forcedColors = forcedColors;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setGeolocation(double latitude, double longitude) {
|
||||
return setGeolocation(new Geolocation(latitude, longitude));
|
||||
}
|
||||
@@ -513,132 +734,297 @@ 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 resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
|
||||
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file.
|
||||
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordHarContent(HarContentPolicy recordHarContent) {
|
||||
this.recordHarContent = recordHarContent;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
|
||||
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setRecordHarMode(HarMode recordHarMode) {
|
||||
this.recordHarMode = recordHarMode;
|
||||
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;
|
||||
}
|
||||
public LaunchPersistentContextOptions setRecordHarUrlFilter(String recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
return this;
|
||||
}
|
||||
public LaunchPersistentContextOptions setRecordHarUrlFilter(Pattern recordHarUrlFilter) {
|
||||
this.recordHarUrlFilter = recordHarUrlFilter;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
|
||||
* <ul>
|
||||
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
|
||||
* registered.</li>
|
||||
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public LaunchPersistentContextOptions setServiceWorkers(ServiceWorkerPolicy serviceWorkers) {
|
||||
this.serviceWorkers = serviceWorkers;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* If 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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
|
||||
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
|
||||
* compatible with 1.2.x).
|
||||
*
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
*/
|
||||
@@ -646,17 +1032,24 @@ public interface BrowserType {
|
||||
return connect(wsEndpoint, null);
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
|
||||
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
|
||||
* compatible with 1.2.x).
|
||||
*
|
||||
* @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.
|
||||
* This method 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.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
|
||||
* BrowserContext defaultContext = browser.contexts().get(0);
|
||||
* Page page = defaultContext.pages().get(0);
|
||||
* }</pre>
|
||||
*
|
||||
* @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}.
|
||||
@@ -665,11 +1058,16 @@ public interface BrowserType {
|
||||
return connectOverCDP(endpointURL, null);
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
* This method 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.
|
||||
* <pre>{@code
|
||||
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
|
||||
* BrowserContext defaultContext = browser.contexts().get(0);
|
||||
* Page page = defaultContext.pages().get(0);
|
||||
* }</pre>
|
||||
*
|
||||
* @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}.
|
||||
@@ -750,7 +1148,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);
|
||||
@@ -764,7 +1163,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);
|
||||
/**
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@@ -28,11 +29,12 @@ import static java.util.Arrays.asList;
|
||||
*/
|
||||
public class CLI {
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Path driver = Driver.ensureDriverInstalled();
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString());
|
||||
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().addAll(asList(args));
|
||||
if (!pb.environment().containsKey("PW_CLI_TARGET_LANG")) {
|
||||
pb.environment().put("PW_CLI_TARGET_LANG", "java");
|
||||
String version = Playwright.class.getPackage().getImplementationVersion();
|
||||
if (version != null) {
|
||||
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
|
||||
}
|
||||
pb.inheritIO();
|
||||
Process process = pb.start();
|
||||
|
||||
@@ -19,11 +19,32 @@ 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. For
|
||||
* each console messages logged in the page there will be corresponding event in the Playwright context.
|
||||
* <pre>{@code
|
||||
* // Listen for all System.out.printlns
|
||||
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
|
||||
*
|
||||
* // Listen for all console events and handle errors
|
||||
* page.onConsoleMessage(msg -> {
|
||||
* if ("error".equals(msg.type()))
|
||||
* System.out.println("Error text: " + msg.text());
|
||||
* });
|
||||
*
|
||||
* // Get the next System.out.println
|
||||
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
|
||||
* // Issue console.log inside the page
|
||||
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
|
||||
* });
|
||||
*
|
||||
* // Deconstruct console.log arguments
|
||||
* msg.args().get(0).jsonValue() // hello
|
||||
* msg.args().get(1).jsonValue() // 42
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ConsoleMessage {
|
||||
/**
|
||||
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsole Page.onConsole()}.
|
||||
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsoleMessage Page.onConsoleMessage()}.
|
||||
*/
|
||||
List<JSHandle> args();
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
|
||||
|
||||
@@ -18,35 +18,34 @@ package com.microsoft.playwright;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
|
||||
*
|
||||
* <p> If {@code downloadsPath} isn't specified, all the downloaded files belonging to the browser context are deleted when the
|
||||
* browser context is closed. And all downloaded files are deleted when the browser closes.
|
||||
* <p> 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.locator("a").click());
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
* <pre>{@code
|
||||
* // wait for download to start
|
||||
* Download download = page.waitForDownload(() -> {
|
||||
* page.click("a");
|
||||
* page.locator("a").click();
|
||||
* });
|
||||
* // wait for download to complete
|
||||
* Path path = download.path();
|
||||
* }</pre>
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the downloaded
|
||||
* content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not performed and user
|
||||
* has no access to the downloaded files.
|
||||
*/
|
||||
public interface Download {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -66,6 +65,9 @@ public interface Download {
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,12 +18,11 @@ package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
|
||||
* <pre>{@code
|
||||
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
|
||||
* FileChooser fileChooser = page.waitForFileChooser(() -> page.locator("upload").click());
|
||||
* fileChooser.setFiles(Paths.get("myfile.pdf"));
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -42,10 +41,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;
|
||||
@@ -65,50 +74,50 @@ public interface FileChooser {
|
||||
Page page();
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path[] files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload[] files, SetFilesOptions options);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* a 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>
|
||||
*
|
||||
* <p> **Converting Locator to FrameLocator**
|
||||
*
|
||||
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/:scope">{@code :scope}</a> CSS selector:
|
||||
* <pre>{@code
|
||||
* Locator frameLocator = locator.frameLocator(':scope');
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FrameLocator {
|
||||
class LocatorOptions {
|
||||
/**
|
||||
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
|
||||
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
|
||||
*
|
||||
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
|
||||
*/
|
||||
public Locator has;
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public Object hasText;
|
||||
|
||||
/**
|
||||
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
|
||||
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
|
||||
*
|
||||
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
|
||||
*/
|
||||
public LocatorOptions setHas(Locator has) {
|
||||
this.has = has;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(String hasText) {
|
||||
this.hasText = hasText;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(Pattern hasText) {
|
||||
this.hasText = hasText;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
default Locator locator(String selector) {
|
||||
return locator(selector, null);
|
||||
}
|
||||
/**
|
||||
* 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, LocatorOptions options);
|
||||
/**
|
||||
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
|
||||
*/
|
||||
FrameLocator nth(int index);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
|
||||
@@ -60,6 +59,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 +73,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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
|
||||
@@ -49,14 +48,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 +80,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 +105,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;
|
||||
@@ -102,10 +122,13 @@ public interface Mouse {
|
||||
}
|
||||
class MoveOptions {
|
||||
/**
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public Integer steps;
|
||||
|
||||
/**
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public MoveOptions setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
return this;
|
||||
@@ -121,10 +144,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 +211,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,6 +40,22 @@ 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 Browser}.
|
||||
*/
|
||||
@@ -48,9 +64,13 @@ public interface Playwright extends AutoCloseable {
|
||||
* This object can be used to launch or connect to Firefox, returning instances of {@code Browser}.
|
||||
*/
|
||||
BrowserType firefox();
|
||||
/**
|
||||
* Exposes API that can be used for the Web API testing.
|
||||
*/
|
||||
APIRequest request();
|
||||
/**
|
||||
* Selectors can be used to install custom selector engines. See <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
|
||||
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
|
||||
*/
|
||||
Selectors selectors();
|
||||
/**
|
||||
@@ -72,8 +92,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,23 @@ public interface Request {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* An object with HTTP headers associated with the request. All header names are lower-case.
|
||||
* An object with the request HTTP headers. The header names are lower-cased. Note that this method does not return
|
||||
* security-related headers, including cookie-related ones. You can use {@link Request#allHeaders Request.allHeaders()} for
|
||||
* complete list of headers that include {@code cookie} information.
|
||||
*/
|
||||
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 +130,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,36 @@ public interface Response {
|
||||
*/
|
||||
Frame frame();
|
||||
/**
|
||||
* Returns the object with HTTP headers associated with the response. All header names are lower-case.
|
||||
* Indicates whether this Response was fulfilled by a Service Worker's Fetch Handler (i.e. via <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith">FetchEvent.respondWith</a>).
|
||||
*/
|
||||
boolean fromServiceWorker();
|
||||
/**
|
||||
* An object with the response HTTP headers. The header names are lower-cased. Note that this method does not return
|
||||
* security-related headers, including cookie-related ones. You can use {@link Response#allHeaders Response.allHeaders()}
|
||||
* for complete list of headers that include {@code cookie} information.
|
||||
*/
|
||||
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 +78,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).
|
||||
*/
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
|
||||
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
|
||||
*
|
||||
* <p> Learn more about <a href="https://playwright.dev/java/docs/network">networking</a>.
|
||||
*/
|
||||
public interface Route {
|
||||
class ResumeOptions {
|
||||
@@ -43,27 +44,98 @@ 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;
|
||||
}
|
||||
}
|
||||
class FallbackOptions {
|
||||
/**
|
||||
* If set changes the request HTTP headers. Header values will be converted to a string.
|
||||
*/
|
||||
public Map<String, String> headers;
|
||||
/**
|
||||
* If set changes the request method (e.g. GET or POST)
|
||||
*/
|
||||
public String method;
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public Object postData;
|
||||
/**
|
||||
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
|
||||
* matching, all the routes are matched using the original request URL.
|
||||
*/
|
||||
public String url;
|
||||
|
||||
/**
|
||||
* If set changes the request HTTP headers. Header values will be converted to a string.
|
||||
*/
|
||||
public FallbackOptions setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request method (e.g. GET or POST)
|
||||
*/
|
||||
public FallbackOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public FallbackOptions setPostData(String postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the post data of request
|
||||
*/
|
||||
public FallbackOptions setPostData(byte[] postData) {
|
||||
this.postData = postData;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
|
||||
* matching, all the routes are matched using the original request URL.
|
||||
*/
|
||||
public FallbackOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FulfillOptions {
|
||||
/**
|
||||
* Optional response body as text.
|
||||
@@ -86,31 +158,63 @@ public interface Route {
|
||||
* is resolved relative to the current working directory.
|
||||
*/
|
||||
public Path path;
|
||||
/**
|
||||
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
|
||||
* using fulfill options.
|
||||
*/
|
||||
public APIResponse response;
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
|
||||
* using fulfill options.
|
||||
*/
|
||||
public FulfillOptions setResponse(APIResponse response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Response status code, defaults to {@code 200}.
|
||||
*/
|
||||
public FulfillOptions setStatus(int status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
@@ -152,8 +256,8 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
@@ -167,13 +271,133 @@ public interface Route {
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "bar"); // set "foo" header
|
||||
* headers.remove("origin"); // remove "origin" header
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.resume(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
void resume(ResumeOptions options);
|
||||
/**
|
||||
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
|
||||
* registered route can always override all the previous ones. In the example below, request will be handled by the
|
||||
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
|
||||
* registered route.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs last.
|
||||
* route.abort();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs second.
|
||||
* route.fallback();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs first.
|
||||
* route.fallback();
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
|
||||
* API calls vs page resources or GET requests vs POST requests as in the example below.
|
||||
* <pre>{@code
|
||||
* // Handle GET requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("GET")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling GET only.
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* // Handle POST requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("POST")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling POST only.
|
||||
* // ...
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
|
||||
* url, method, headers and postData of the request.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
default void fallback() {
|
||||
fallback(null);
|
||||
}
|
||||
/**
|
||||
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
|
||||
* registered route can always override all the previous ones. In the example below, request will be handled by the
|
||||
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
|
||||
* registered route.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs last.
|
||||
* route.abort();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs second.
|
||||
* route.fallback();
|
||||
* });
|
||||
*
|
||||
* page.route("**\/*", route -> {
|
||||
* // Runs first.
|
||||
* route.fallback();
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
|
||||
* API calls vs page resources or GET requests vs POST requests as in the example below.
|
||||
* <pre>{@code
|
||||
* // Handle GET requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("GET")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling GET only.
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* // Handle POST requests.
|
||||
* page.route("**\/*", route -> {
|
||||
* if (!route.request().method().equals("POST")) {
|
||||
* route.fallback();
|
||||
* return;
|
||||
* }
|
||||
* // Handling POST only.
|
||||
* // ...
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
|
||||
* url, method, headers and postData of the request.
|
||||
* <pre>{@code
|
||||
* page.route("**\/*", route -> {
|
||||
* // Override headers
|
||||
* Map<String, String> headers = new HashMap<>(route.request().headers());
|
||||
* headers.put("foo", "foo-value"); // set "foo" header
|
||||
* headers.remove("bar"); // remove "bar" header
|
||||
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
|
||||
* });
|
||||
* }</pre>
|
||||
*/
|
||||
void fallback(FallbackOptions options);
|
||||
/**
|
||||
* Fulfills route's request with given response.
|
||||
*
|
||||
@@ -190,7 +414,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 +436,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);
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Selectors can be used to install custom selector engines. See <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
|
||||
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
|
||||
*/
|
||||
public interface Selectors {
|
||||
class RegisterOptions {
|
||||
@@ -32,6 +31,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;
|
||||
@@ -40,7 +44,7 @@ public interface Selectors {
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -57,17 +61,17 @@ 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\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
*/
|
||||
default void register(String name, String script) {
|
||||
register(name, script, null);
|
||||
@@ -75,7 +79,7 @@ public interface Selectors {
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -92,23 +96,23 @@ 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\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
*/
|
||||
void register(String name, String script, RegisterOptions options);
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -125,17 +129,17 @@ 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\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
*/
|
||||
default void register(String name, Path script) {
|
||||
register(name, script, null);
|
||||
@@ -143,7 +147,7 @@ public interface Selectors {
|
||||
/**
|
||||
* An example of registering selector engine that queries elements based on a tag name:
|
||||
* <pre>{@code
|
||||
* // Script that evaluates to a selector engine instance.
|
||||
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
* String createTagNameEngine = "{\n" +
|
||||
* " // Returns the first element matching given selector in the root's subtree.\n" +
|
||||
* " query(root, selector) {\n" +
|
||||
@@ -160,17 +164,17 @@ 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\"");
|
||||
* page.locator("tag=div >> text=\"Click me\"").click();
|
||||
* // 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>
|
||||
*
|
||||
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
|
||||
* contain {@code [a-zA-Z0-9_]} characters.
|
||||
* @param script Script that evaluates to a selector engine instance.
|
||||
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
|
||||
*/
|
||||
void register(String name, Path script, RegisterOptions options);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
|
||||
|
||||
@@ -17,22 +17,21 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* API for collecting and saving Playwright traces. Playwright traces can be opened using the Playwright CLI after
|
||||
* Playwright script runs.
|
||||
* 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 with specifying the folder traces will be stored in:
|
||||
* <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()
|
||||
* context.tracing().start(new Tracing.StartOptions()
|
||||
* .setScreenshots(true)
|
||||
* .setSnapshots(true);
|
||||
* .setSnapshots(true));
|
||||
* Page page = context.newPage();
|
||||
* page.goto("https://playwright.dev");
|
||||
* context.tracing.stop(new Tracing.StopOptions()
|
||||
* page.navigate("https://playwright.dev");
|
||||
* context.tracing().stop(new Tracing.StopOptions()
|
||||
* .setPath(Paths.get("trace.zip")));
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -48,43 +47,120 @@ public interface Tracing {
|
||||
*/
|
||||
public Boolean screenshots;
|
||||
/**
|
||||
* Whether to capture DOM snapshot on every action.
|
||||
* If this option is true tracing will
|
||||
* <ul>
|
||||
* <li> capture DOM snapshot on every action</li>
|
||||
* <li> record network activity</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Boolean snapshots;
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
*/
|
||||
public Boolean sources;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* If this option is true tracing will
|
||||
* <ul>
|
||||
* <li> capture DOM snapshot on every action</li>
|
||||
* <li> record network activity</li>
|
||||
* </ul>
|
||||
*/
|
||||
public StartOptions setSnapshots(boolean snapshots) {
|
||||
this.snapshots = snapshots;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
*/
|
||||
public StartOptions setSources(boolean sources) {
|
||||
this.sources = sources;
|
||||
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 name.
|
||||
* 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()
|
||||
* context.tracing().start(new Tracing.StartOptions()
|
||||
* .setScreenshots(true)
|
||||
* .setSnapshots(true);
|
||||
* .setSnapshots(true));
|
||||
* Page page = context.newPage();
|
||||
* page.goto('https://playwright.dev');
|
||||
* context.tracing.stop(new Tracing.StopOptions()
|
||||
* page.navigate("https://playwright.dev");
|
||||
* context.tracing().stop(new Tracing.StopOptions()
|
||||
* .setPath(Paths.get("trace.zip")));
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -94,16 +170,68 @@ public interface Tracing {
|
||||
/**
|
||||
* Start tracing.
|
||||
* <pre>{@code
|
||||
* context.tracing.start(new Tracing.StartOptions()
|
||||
* context.tracing().start(new Tracing.StartOptions()
|
||||
* .setScreenshots(true)
|
||||
* .setSnapshots(true);
|
||||
* .setSnapshots(true));
|
||||
* Page page = context.newPage();
|
||||
* page.goto('https://playwright.dev');
|
||||
* context.tracing.stop(new Tracing.StopOptions()
|
||||
* 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.locator("text=Get Started").click();
|
||||
* // 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.locator("text=Get Started").click();
|
||||
* // 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.
|
||||
*/
|
||||
@@ -114,5 +242,15 @@ public interface Tracing {
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -72,10 +71,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 +98,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;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -52,6 +51,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;
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code APIResponse}
|
||||
* in the tests. A new instance of {@code APIResponseAssertions} 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() {
|
||||
* ...
|
||||
* APIResponse response = page.request().get('https://playwright.dev');
|
||||
* assertThat(response).isOK();
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public interface APIResponseAssertions {
|
||||
/**
|
||||
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
|
||||
* successful:
|
||||
* <pre>{@code
|
||||
* assertThat(response).not().isOK();
|
||||
* }</pre>
|
||||
*/
|
||||
APIResponseAssertions not();
|
||||
/**
|
||||
* Ensures the response status code is within {@code 200..299} range.
|
||||
* <pre>{@code
|
||||
* assertThat(response).isOK();
|
||||
* }</pre>
|
||||
*/
|
||||
void isOK();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 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 PageAssertions} 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.locator("#login").click();
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* 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 URL string 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 URL string 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 URL string 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 URL string or RegExp.
|
||||
*/
|
||||
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
|
||||
}
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.Locator;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.AssertionsTimeout;
|
||||
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
|
||||
/**
|
||||
* Playwright gives you Web-First Assertions with convenience methods for creating assertions that will wait and retry
|
||||
* 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.locator("#submit-button").click();
|
||||
* 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.
|
||||
*/
|
||||
public interface PlaywrightAssertions {
|
||||
/**
|
||||
* Creates a {@code APIResponseAssertions} object for the given {@code APIResponse}.
|
||||
* <pre>{@code
|
||||
* PlaywrightAssertions.assertThat(response).isOK();
|
||||
* }</pre>
|
||||
*
|
||||
* @param response {@code APIResponse} object to use for assertions.
|
||||
*/
|
||||
static APIResponseAssertions assertThat(APIResponse response) {
|
||||
return new APIResponseAssertionsImpl(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes default timeout for Playwright assertions from 5 seconds to the specified value.
|
||||
* <pre>{@code
|
||||
* PlaywrightAssertions.setDefaultAssertionTimeout(30_000);
|
||||
* }</pre>
|
||||
*
|
||||
* @param timeout Timeout in milliseconds.
|
||||
*/
|
||||
static void setDefaultAssertionTimeout(double milliseconds) {
|
||||
AssertionsTimeout.setDefaultTimeout(milliseconds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequestContext;
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.RequestOptions;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.toFilePayload;
|
||||
|
||||
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
|
||||
private final TracingImpl tracing;
|
||||
|
||||
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse delete(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "DELETE"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
|
||||
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse fetch(Request request, RequestOptions optionsArg) {
|
||||
RequestOptionsImpl options = (RequestOptionsImpl) optionsArg;
|
||||
if (options == null) {
|
||||
options = new RequestOptionsImpl();
|
||||
}
|
||||
if (options.method == null) {
|
||||
options.method = request.method();
|
||||
}
|
||||
if (options.headers == null) {
|
||||
options.headers = request.headers();
|
||||
}
|
||||
if (options.data == null && options.form == null && options.multipart == null) {
|
||||
options.data = request.postDataBuffer();
|
||||
}
|
||||
return fetch(request.url(), options);
|
||||
}
|
||||
|
||||
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
|
||||
if (options == null) {
|
||||
options = new RequestOptionsImpl();
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("url", url);
|
||||
if (options.params != null) {
|
||||
Map<String, String> queryParams = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, ?> e : options.params.entrySet()) {
|
||||
queryParams.put(e.getKey(), "" + e.getValue());
|
||||
}
|
||||
params.add("params", toNameValueArray(queryParams));
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", toProtocol(options.headers));
|
||||
}
|
||||
|
||||
if (options.data != null) {
|
||||
byte[] bytes = null;
|
||||
if (options.data instanceof byte[]) {
|
||||
bytes = (byte[]) options.data;
|
||||
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
|
||||
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
if (bytes == null) {
|
||||
params.add("jsonData", gson().toJsonTree(options.data));
|
||||
} else {
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
}
|
||||
if (options.form != null) {
|
||||
params.add("formData", toNameValueArray(options.form.fields));
|
||||
}
|
||||
if (options.multipart != null) {
|
||||
params.add("multipartData", serializeMultipartData(options.multipart.fields));
|
||||
}
|
||||
if (options.timeout != null) {
|
||||
params.addProperty("timeout", options.timeout);
|
||||
}
|
||||
if (options.failOnStatusCode != null) {
|
||||
params.addProperty("failOnStatusCode", options.failOnStatusCode);
|
||||
}
|
||||
if (options.ignoreHTTPSErrors != null) {
|
||||
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
|
||||
}
|
||||
if (options.maxRedirects != null) {
|
||||
if (options.maxRedirects < 0) {
|
||||
throw new PlaywrightException("'maxRedirects' should be greater than or equal to '0'");
|
||||
}
|
||||
params.addProperty("maxRedirects", options.maxRedirects);
|
||||
}
|
||||
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
|
||||
return new APIResponseImpl(this, json.getAsJsonObject("response"));
|
||||
}
|
||||
|
||||
private static boolean isJsonContentType(Map<String, String> headers) {
|
||||
if (headers == null) {
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
if ("content-type".equalsIgnoreCase(e.getKey())) {
|
||||
return "application/json".equals(e.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static JsonArray serializeMultipartData(Map<String, Object> data) {
|
||||
JsonArray result = new JsonArray();
|
||||
for (Map.Entry<String, Object> e : data.entrySet()) {
|
||||
FilePayload filePayload = null;
|
||||
if (e.getValue() instanceof FilePayload) {
|
||||
filePayload = (FilePayload) e.getValue();
|
||||
} else if (e.getValue() instanceof Path) {
|
||||
filePayload = toFilePayload((Path) e.getValue());
|
||||
} else if (e.getValue() instanceof File) {
|
||||
filePayload = toFilePayload(((File) e.getValue()).toPath());
|
||||
}
|
||||
JsonObject item = new JsonObject();
|
||||
item.addProperty("name", e.getKey());
|
||||
if (filePayload == null) {
|
||||
item.addProperty("value", "" + e.getValue());
|
||||
} else {
|
||||
item.add("file", toProtocol(filePayload));
|
||||
}
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse get(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "GET"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse head(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "HEAD"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse patch(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "PATCH"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse post(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "POST"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse put(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "PUT"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String storageState(StorageStateOptions options) {
|
||||
return withLogging("APIRequestContext.storageState", () -> {
|
||||
JsonElement json = sendMessage("storageState");
|
||||
String storageState = json.toString();
|
||||
if (options != null && options.path != null) {
|
||||
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
|
||||
}
|
||||
return storageState;
|
||||
});
|
||||
}
|
||||
|
||||
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
|
||||
RequestOptionsImpl impl = (RequestOptionsImpl) options;
|
||||
if (impl == null) {
|
||||
impl = new RequestOptionsImpl();
|
||||
}
|
||||
if (impl.method == null) {
|
||||
impl.method = method;
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequest;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class APIRequestImpl implements APIRequest {
|
||||
private final PlaywrightImpl playwright;
|
||||
|
||||
APIRequestImpl(PlaywrightImpl playwright) {
|
||||
this.playwright = playwright;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequestContextImpl newContext(NewContextOptions options) {
|
||||
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
|
||||
}
|
||||
|
||||
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new NewContextOptions();
|
||||
}
|
||||
if (options.storageStatePath != null) {
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(options.storageStatePath);
|
||||
options.storageState = new String(bytes, StandardCharsets.UTF_8);
|
||||
options.storageStatePath = null;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read storage state from file", e);
|
||||
}
|
||||
}
|
||||
JsonObject storageState = null;
|
||||
if (options.storageState != null) {
|
||||
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
|
||||
options.storageState = null;
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (storageState != null) {
|
||||
params.add("storageState", storageState);
|
||||
}
|
||||
|
||||
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
|
||||
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.APIResponse;
|
||||
import com.microsoft.playwright.assertions.APIResponseAssertions;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class APIResponseAssertionsImpl implements APIResponseAssertions {
|
||||
private final APIResponse actual;
|
||||
private final boolean isNot;
|
||||
|
||||
APIResponseAssertionsImpl(APIResponse response, boolean isNot) {
|
||||
this.actual = response;
|
||||
this.isNot = isNot;
|
||||
}
|
||||
|
||||
public APIResponseAssertionsImpl(APIResponse response) {
|
||||
this(response, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponseAssertions not() {
|
||||
return new APIResponseAssertionsImpl(actual, !isNot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isOK() {
|
||||
if (actual.ok() == !isNot) {
|
||||
return;
|
||||
}
|
||||
String message = "Response status expected to be within [200..299] range, was " + actual.status();
|
||||
if (isNot) {
|
||||
message = message.replace("expected to", "expected not to");
|
||||
}
|
||||
List<String> logList = ((APIResponseImpl) actual).fetchLog();
|
||||
String log = String.join("\n", logList);
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + log;
|
||||
}
|
||||
|
||||
String contentType = actual.headers().get("content-type");
|
||||
boolean isTextEncoding = contentType == null ? false : isTextualMimeType(contentType);
|
||||
String responseText = "";
|
||||
if (isTextEncoding) {
|
||||
String text = actual.text();
|
||||
if (text != null) {
|
||||
responseText = "\nResponse text:\n" + (text.length() > 1000 ? text.substring(0, 1000) : text);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionFailedError(message + log + responseText);
|
||||
}
|
||||
static boolean isTextualMimeType(String mimeType) {
|
||||
return Pattern.matches("^(text/.*?|application/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image/svg(\\+xml)?|application/.*?(\\+json|\\+xml))(;\\s*charset=.*)?$", mimeType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.microsoft.playwright.APIResponse;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class APIResponseImpl implements APIResponse {
|
||||
final APIRequestContextImpl context;
|
||||
private final JsonObject initializer;
|
||||
private final RawHeaders headers;
|
||||
|
||||
APIResponseImpl(APIRequestContextImpl apiRequestContext, JsonObject response) {
|
||||
context = apiRequestContext;
|
||||
initializer = response;
|
||||
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] body() {
|
||||
return context.withLogging("APIResponse.body", () -> {
|
||||
try {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
|
||||
if (!json.has("binary")) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
return Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
} catch (PlaywrightException e) {
|
||||
if (isSafeCloseError(e)) {
|
||||
throw new PlaywrightException("Response has been disposed");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
context.withLogging("APIResponse.dispose", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
context.sendMessage("disposeAPIResponse", params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> headers() {
|
||||
return headers.headers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpHeader> headersArray() {
|
||||
return headers.headersArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ok() {
|
||||
int status = status();
|
||||
return status == 0 || (status >= 200 && status <= 299);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int status() {
|
||||
return initializer.get("status").getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String statusText() {
|
||||
return initializer.get("statusText").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String text() {
|
||||
return new String(body(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
String fetchUid() {
|
||||
return initializer.get("fetchUid").getAsString();
|
||||
}
|
||||
|
||||
List<String> fetchLog() {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("fetchUid", fetchUid());
|
||||
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
|
||||
JsonArray log = json.get("log").getAsJsonArray();
|
||||
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,6 +40,10 @@ class ArtifactImpl extends ChannelOwner {
|
||||
return stream.stream();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
sendMessage("cancel");
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
sendMessage("delete");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
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 = AssertionsTimeout.defaultTimeout;
|
||||
}
|
||||
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);
|
||||
}
|
||||
ValueWrapper expectedValue = formatValue(expected);
|
||||
ValueWrapper actualValue = formatValue(actual);
|
||||
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
|
||||
throw new AssertionFailedError(message + log, expectedValue, actualValue);
|
||||
}
|
||||
}
|
||||
|
||||
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 = toJsRegexFlags(pattern);
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
}
|
||||
@@ -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.impl;
|
||||
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
public class AssertionsTimeout {
|
||||
static double defaultTimeout = 5_000;
|
||||
|
||||
public static void setDefaultTimeout(double ms) {
|
||||
if (ms < 0) {
|
||||
throw new PlaywrightException("Timeout cannot be negative");
|
||||
}
|
||||
defaultTimeout = ms;
|
||||
}
|
||||
}
|
||||
@@ -20,22 +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.BindingCallback;
|
||||
import com.microsoft.playwright.options.Cookie;
|
||||
import com.microsoft.playwright.options.FunctionCallback;
|
||||
import com.microsoft.playwright.options.Geolocation;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -45,6 +43,7 @@ import static java.util.Arrays.asList;
|
||||
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final BrowserImpl browser;
|
||||
private final TracingImpl tracing;
|
||||
private final APIRequestContextImpl request;
|
||||
final List<PageImpl> pages = new ArrayList<>();
|
||||
final Router routes = new Router();
|
||||
private boolean isClosedOrClosing;
|
||||
@@ -53,6 +52,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
final Map<String, HarRecorder> harRecorders = new HashMap<>();
|
||||
|
||||
static class HarRecorder {
|
||||
final Path path;
|
||||
final HarContentPolicy contentPolicy;
|
||||
|
||||
HarRecorder(Path har, HarContentPolicy policy) {
|
||||
path = har;
|
||||
contentPolicy = policy;
|
||||
}
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
@@ -70,7 +81,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} else {
|
||||
browser = null;
|
||||
}
|
||||
this.tracing = new TracingImpl(this);
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
tracing.isRemote = browser != null && browser.isRemote;
|
||||
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
|
||||
}
|
||||
|
||||
void setRecordHar(Path path, HarContentPolicy policy) {
|
||||
if (path != null) {
|
||||
harRecorders.put("", new HarRecorder(path, policy));
|
||||
}
|
||||
}
|
||||
|
||||
void setBaseUrl(String spec) {
|
||||
try {
|
||||
this.baseUrl = new URL(spec);
|
||||
} catch (MalformedURLException e) {
|
||||
this.baseUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,9 +160,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
listeners.remove(EventType.RESPONSE, handler);
|
||||
}
|
||||
|
||||
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double 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));
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
|
||||
waitables.add(new WaitableContextClose<>());
|
||||
waitables.add(timeoutSettings.createWaitable(timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
@@ -143,14 +170,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public Page waitForPage(WaitForPageOptions options, Runnable code) {
|
||||
return withWaitLogging("BrowserContext.close", () -> waitForPageImpl(options, code));
|
||||
return withWaitLogging("BrowserContext.close", logger -> 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
|
||||
@@ -160,7 +187,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
@Override
|
||||
public List<Cookie> cookies(String url) {
|
||||
return cookies(url == null ? new ArrayList<>() : asList(url));
|
||||
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
|
||||
}
|
||||
|
||||
private void closeImpl() {
|
||||
@@ -169,6 +196,34 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
isClosedOrClosing = true;
|
||||
try {
|
||||
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", entry.getKey());
|
||||
JsonObject json = sendMessage("harExport", params).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;
|
||||
}
|
||||
|
||||
// Server side will compress artifact if content is attach or if file is .zip.
|
||||
HarRecorder harParams = entry.getValue();
|
||||
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
|
||||
boolean needCompressed = harParams.path.toString().endsWith(".zip");
|
||||
if (isCompressed && !needCompressed) {
|
||||
String tmpPath = harParams.path + ".tmp";
|
||||
artifact.saveAs(Paths.get(tmpPath));
|
||||
JsonObject unzipParams = new JsonObject();
|
||||
unzipParams.addProperty("zipFile", tmpPath);
|
||||
unzipParams.addProperty("harFile", harParams.path.toString());
|
||||
connection.localUtils.sendMessage("harUnzip", unzipParams);
|
||||
} else {
|
||||
artifact.saveAs(harParams.path);
|
||||
}
|
||||
artifact.delete();
|
||||
}
|
||||
|
||||
sendMessage("close");
|
||||
} catch (PlaywrightException e) {
|
||||
if (!isSafeCloseError(e)) {
|
||||
@@ -306,23 +361,43 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(String url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public APIRequestContextImpl request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Pattern url, Consumer<Route> handler) {
|
||||
route(new UrlMatcher(url), handler);
|
||||
public void route(String url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(baseUrl, url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void route(Predicate<String> 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);
|
||||
}
|
||||
|
||||
private void route(UrlMatcher matcher, Consumer<Route> handler) {
|
||||
@Override
|
||||
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
|
||||
route(new UrlMatcher(url), handler, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void routeFromHAR(Path har, RouteFromHAROptions options) {
|
||||
if (options == null) {
|
||||
options = new RouteFromHAROptions();
|
||||
}
|
||||
if (options.update != null && options.update) {
|
||||
recordIntoHar(null, har, options);
|
||||
return;
|
||||
}
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url);
|
||||
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
|
||||
onClose(context -> harRouter.dispose());
|
||||
route(matcher, route -> harRouter.handle(route), null);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -331,6 +406,22 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
});
|
||||
}
|
||||
|
||||
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
|
||||
JsonObject params = new JsonObject();
|
||||
if (page != null) {
|
||||
params.add("page", page.toProtocolRef());
|
||||
}
|
||||
JsonObject jsonOptions = new JsonObject();
|
||||
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
|
||||
jsonOptions.addProperty("content", HarContentPolicy.ATTACH.name().toLowerCase());
|
||||
jsonOptions.addProperty("mode", HarMode.MINIMAL.name().toLowerCase());
|
||||
addHarUrlFilter(jsonOptions, options.url);
|
||||
params.add("options", jsonOptions);
|
||||
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
|
||||
String harId = json.get("harId").getAsString();
|
||||
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultNavigationTimeout(double timeout) {
|
||||
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
|
||||
@@ -400,13 +491,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tracing tracing() {
|
||||
public TracingImpl 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
|
||||
@@ -433,14 +524,28 @@ 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(RouteImpl route) {
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
maybeDisableNetworkInterception();
|
||||
}
|
||||
if (!route.isHandled()){
|
||||
route.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
sendMessage("pause");
|
||||
}
|
||||
@@ -448,11 +553,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
handleRoute(route);
|
||||
} else if ("page".equals(event)) {
|
||||
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
|
||||
pages.add(page);
|
||||
@@ -477,6 +579,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} 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();
|
||||
}
|
||||
@@ -491,6 +594,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} 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();
|
||||
}
|
||||
@@ -519,4 +623,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
|
||||
WritableStream createTempFile(String name) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("name", name);
|
||||
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,20 +19,21 @@ 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;
|
||||
import com.microsoft.playwright.BrowserContext;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class BrowserImpl extends ChannelOwner implements Browser {
|
||||
final Set<BrowserContextImpl> contexts = new HashSet<>();
|
||||
@@ -40,6 +41,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
boolean isRemote;
|
||||
boolean isConnectedOverWebSocket;
|
||||
private boolean isConnected = true;
|
||||
BrowserTypeImpl browserType;
|
||||
|
||||
enum EventType {
|
||||
DISCONNECTED,
|
||||
@@ -59,6 +61,11 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
listeners.remove(EventType.DISCONNECTED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserType browserType() {
|
||||
return browserType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
withLogging("Browser.close", () -> closeImpl());
|
||||
@@ -71,7 +78,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to close browser connection", e);
|
||||
}
|
||||
notifyRemoteClosed();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -112,6 +118,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
private BrowserContextImpl newContextImpl(NewContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new NewContextOptions();
|
||||
} else {
|
||||
// Make a copy so that we can nullify some fields below.
|
||||
options = convertType(options, NewContextOptions.class);
|
||||
}
|
||||
if (options.storageStatePath != null) {
|
||||
try {
|
||||
@@ -127,21 +136,50 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
|
||||
options.storageState = null;
|
||||
}
|
||||
JsonObject recordHar = null;
|
||||
Path recordHarPath = options.recordHarPath;
|
||||
HarContentPolicy harContentPolicy = null;
|
||||
if (options.recordHarPath != null) {
|
||||
recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarContent != null) {
|
||||
harContentPolicy = options.recordHarContent;
|
||||
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
|
||||
harContentPolicy = HarContentPolicy.OMIT;
|
||||
}
|
||||
if (harContentPolicy != null) {
|
||||
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
|
||||
}
|
||||
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
|
||||
options.recordHarPath = null;
|
||||
options.recordHarMode = null;
|
||||
options.recordHarOmitContent = null;
|
||||
options.recordHarContent = null;
|
||||
options.recordHarUrlFilter = null;
|
||||
} else {
|
||||
if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarUrlFilter != null) {
|
||||
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarContent != null) {
|
||||
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (storageState != null) {
|
||||
params.add("storageState", storageState);
|
||||
}
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
if (recordHar != null) {
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
@@ -165,12 +203,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.setRecordHar(recordHarPath, harContentPolicy);
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
@@ -191,9 +230,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
if (page != null) {
|
||||
JsonObject jsonPage = new JsonObject();
|
||||
jsonPage.addProperty("guid", ((PageImpl) page).guid);
|
||||
params.add("page", jsonPage);
|
||||
params.add("page", ((PageImpl) page).toProtocolRef());
|
||||
}
|
||||
sendMessage("startTracing", params);
|
||||
}
|
||||
@@ -209,7 +246,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
}
|
||||
|
||||
private Page newPageImpl(NewPageOptions options) {
|
||||
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
|
||||
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
|
||||
PageImpl page = context.newPage();
|
||||
page.ownedContext = context;
|
||||
context.ownerPage = page;
|
||||
|
||||
@@ -16,24 +16,25 @@
|
||||
|
||||
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;
|
||||
import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
LocalUtils localUtils;
|
||||
|
||||
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
@@ -49,7 +50,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonElement result = sendMessage("launch", params);
|
||||
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.browserType = this;
|
||||
return browser;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,51 +61,57 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
|
||||
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
|
||||
try {
|
||||
Duration timeout = Duration.ofDays(1);
|
||||
Map<String, String> headers = Collections.emptyMap();
|
||||
Duration slowMo = null;
|
||||
if (options != null) {
|
||||
if (options.timeout != null) {
|
||||
timeout = Duration.ofMillis(Math.round(options.timeout));
|
||||
}
|
||||
if (options.headers != null) {
|
||||
headers = options.headers;
|
||||
}
|
||||
if (options.slowMo != null) {
|
||||
slowMo = Duration.ofMillis(options.slowMo.intValue());
|
||||
}
|
||||
}
|
||||
WebSocketTransport transport = new WebSocketTransport(new URI(wsEndpoint), headers, timeout, slowMo);
|
||||
Connection connection = new Connection(transport);
|
||||
PlaywrightImpl playwright = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
|
||||
if (!playwright.initializer.has("preLaunchedBrowser")) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
|
||||
}
|
||||
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
|
||||
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.isConnectedOverWebSocket = true;
|
||||
Consumer<WebSocketTransport> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
transport.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
playwright.unregisterSelectors();
|
||||
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);
|
||||
|
||||
if (!params.has("headers")) {
|
||||
params.add("headers", new JsonObject());
|
||||
}
|
||||
JsonObject headers = params.get("headers").getAsJsonObject();
|
||||
boolean foundBrowserHeader = false;
|
||||
for (String name : headers.keySet()) {
|
||||
if ("x-playwright-browser".equalsIgnoreCase(name)) {
|
||||
foundBrowserHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundBrowserHeader) {
|
||||
headers.addProperty("x-playwright-browser", name());
|
||||
}
|
||||
|
||||
JsonObject json = sendMessage("connect", params).getAsJsonObject();
|
||||
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
|
||||
Connection connection = new Connection(pipe, this.connection.env);
|
||||
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;
|
||||
browser.browserType = this;
|
||||
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
|
||||
@@ -119,12 +128,12 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("sdkLanguage", "java");
|
||||
params.addProperty("endpointURL", endpointURL);
|
||||
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
|
||||
|
||||
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.browserType = this;
|
||||
if (json.has("defaultContext")) {
|
||||
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
|
||||
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
|
||||
@@ -146,20 +155,52 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
|
||||
if (options == null) {
|
||||
options = new LaunchPersistentContextOptions();
|
||||
} else {
|
||||
// Make a copy so that we can nullify some fields below.
|
||||
options = convertType(options, LaunchPersistentContextOptions.class);
|
||||
}
|
||||
JsonObject recordHar = null;
|
||||
Path recordHarPath = options.recordHarPath;
|
||||
HarContentPolicy harContentPolicy = null;
|
||||
if (options.recordHarPath != null) {
|
||||
recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarContent != null) {
|
||||
harContentPolicy = options.recordHarContent;
|
||||
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
|
||||
harContentPolicy = HarContentPolicy.OMIT;
|
||||
}
|
||||
if (harContentPolicy != null) {
|
||||
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
|
||||
}
|
||||
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
|
||||
options.recordHarPath = null;
|
||||
options.recordHarMode = null;
|
||||
options.recordHarOmitContent = null;
|
||||
options.recordHarContent = null;
|
||||
options.recordHarUrlFilter = null;
|
||||
} else {
|
||||
if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarUrlFilter != null) {
|
||||
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarMode != null) {
|
||||
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordHarContent != null) {
|
||||
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("userDataDir", userDataDir.toString());
|
||||
if (options.recordHarPath != null) {
|
||||
JsonObject recordHar = new JsonObject();
|
||||
recordHar.addProperty("path", options.recordHarPath.toString());
|
||||
if (options.recordHarOmitContent != null) {
|
||||
recordHar.addProperty("omitContent", true);
|
||||
}
|
||||
params.remove("recordHarPath");
|
||||
params.remove("recordHarOmitContent");
|
||||
if (recordHar != null) {
|
||||
params.add("recordHar", recordHar);
|
||||
} else if (options.recordHarOmitContent != null) {
|
||||
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
|
||||
}
|
||||
if (options.recordVideoDir != null) {
|
||||
JsonObject recordVideo = new JsonObject();
|
||||
@@ -183,12 +224,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.setRecordHar(recordHarPath, harContentPolicy);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ import com.google.gson.JsonObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
class ChannelOwner extends LoggingSupport {
|
||||
final Connection connection;
|
||||
private final ChannelOwner parent;
|
||||
private ChannelOwner parent;
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
|
||||
final String type;
|
||||
@@ -68,10 +70,25 @@ class ChannelOwner extends LoggingSupport {
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
<T> T withWaitLogging(String apiName, Supplier<T> code) {
|
||||
return super.withLogging(apiName, new WaitForEventLogger<>(this, apiName, code));
|
||||
void adopt(ChannelOwner child) {
|
||||
child.parent.objects.remove(child.guid);
|
||||
objects.put(child.guid, child);
|
||||
child.parent = this;
|
||||
}
|
||||
|
||||
<T> T withWaitLogging(String apiName, Function<Logger, 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);
|
||||
@@ -99,4 +116,10 @@ class ChannelOwner extends LoggingSupport {
|
||||
|
||||
void handleEvent(String event, JsonObject parameters) {
|
||||
}
|
||||
|
||||
JsonObject toProtocolRef() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,17 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.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;
|
||||
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 {
|
||||
@@ -66,32 +56,48 @@ public class Connection {
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
private final Root root;
|
||||
private int lastId = 0;
|
||||
private final Path srcDir;
|
||||
private final StackTraceCollector stackTraceCollector;
|
||||
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");
|
||||
}
|
||||
LocalUtils localUtils;
|
||||
final Map<String, String> env;
|
||||
|
||||
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) {
|
||||
Connection(Transport transport, Map<String, String> env) {
|
||||
this.env = env;
|
||||
if (isLogging) {
|
||||
transport = new TransportLogger(transport);
|
||||
}
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
if (srcRoot == null) {
|
||||
srcDir = null;
|
||||
} else {
|
||||
srcDir = Paths.get(srcRoot);
|
||||
if (!Files.exists(srcDir)) {
|
||||
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
|
||||
}
|
||||
}
|
||||
stackTraceCollector = StackTraceCollector.createFromEnv(env);
|
||||
}
|
||||
|
||||
boolean isCollectingStacks() {
|
||||
return stackTraceCollector != null;
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
String previous = apiName;
|
||||
apiName = name;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
@@ -106,45 +112,6 @@ public class Connection {
|
||||
return internalSendMessage(guid, method, params);
|
||||
}
|
||||
|
||||
private String sourceFile(StackTraceElement frame) {
|
||||
String pkg = frame.getClassName();
|
||||
int lastDot = pkg.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
pkg = "";
|
||||
} else {
|
||||
pkg = frame.getClassName().substring(0, lastDot + 1);
|
||||
}
|
||||
pkg = pkg.replace('.', File.separatorChar);
|
||||
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
|
||||
}
|
||||
|
||||
private JsonArray currentStackTrace() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
|
||||
int index = 0;
|
||||
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
|
||||
index++;
|
||||
};
|
||||
// Find Playwright API call
|
||||
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
|
||||
// hack for tests
|
||||
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
JsonArray jsonStack = new JsonArray();
|
||||
for (; index < stack.length; index++) {
|
||||
StackTraceElement frame = stack[index];
|
||||
JsonObject jsonFrame = new JsonObject();
|
||||
jsonFrame.addProperty("file", sourceFile(frame));
|
||||
jsonFrame.addProperty("line", frame.getLineNumber());
|
||||
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
|
||||
jsonStack.add(jsonFrame);
|
||||
}
|
||||
return jsonStack;
|
||||
}
|
||||
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
|
||||
int id = ++lastId;
|
||||
WaitableResult<JsonElement> result = new WaitableResult<>();
|
||||
@@ -154,24 +121,28 @@ public class Connection {
|
||||
message.addProperty("guid", guid);
|
||||
message.addProperty("method", method);
|
||||
message.add("params", params);
|
||||
if (srcDir != null) {
|
||||
JsonObject metadata = new JsonObject();
|
||||
metadata.add("stack", currentStackTrace());
|
||||
message.add("metadata", metadata);
|
||||
JsonObject metadata = new JsonObject();
|
||||
if (apiName == null) {
|
||||
metadata.addProperty("internal", true);
|
||||
} else {
|
||||
metadata.addProperty("apiName", apiName);
|
||||
// All but first message in an API call are considered internal and will be hidden from the inspector.
|
||||
apiName = null;
|
||||
if (stackTraceCollector != null) {
|
||||
metadata.add("stack", stackTraceCollector.currentStackTrace());
|
||||
}
|
||||
}
|
||||
String messageString = gson().toJson(message);
|
||||
if (isLogging) {
|
||||
logWithTimestamp("SEND ► " + messageString);
|
||||
}
|
||||
transport.send(messageString);
|
||||
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();
|
||||
}
|
||||
|
||||
LocalUtils localUtils() {
|
||||
return localUtils;
|
||||
}
|
||||
|
||||
public <T> T getExistingObject(String guid) {
|
||||
@@ -190,16 +161,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;
|
||||
}
|
||||
if (isLogging) {
|
||||
logWithTimestamp("◀ RECV " + messageString);
|
||||
}
|
||||
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) {
|
||||
@@ -233,18 +201,24 @@ public class Connection {
|
||||
createRemoteObject(message.guid, message.params);
|
||||
return;
|
||||
}
|
||||
if (message.method.equals("__dispose__")) {
|
||||
ChannelOwner object = objects.get(message.guid);
|
||||
if (object == null) {
|
||||
throw new PlaywrightException("Cannot find object to dispose: " + message.guid);
|
||||
}
|
||||
object.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelOwner object = objects.get(message.guid);
|
||||
if (object == null) {
|
||||
throw new PlaywrightException("Cannot find object to call " + message.method + ": " + message.guid);
|
||||
}
|
||||
if (message.method.equals("__adopt__")) {
|
||||
String childGuid = message.params.get("guid").getAsString();
|
||||
ChannelOwner child = objects.get(childGuid);
|
||||
if (child == null) {
|
||||
throw new PlaywrightException("Unknown new child: " + childGuid);
|
||||
}
|
||||
object.adopt(child);
|
||||
return;
|
||||
}
|
||||
if (message.method.equals("__dispose__")) {
|
||||
object.disconnect();
|
||||
return;
|
||||
}
|
||||
object.handleEvent(message.method, message.params);
|
||||
}
|
||||
|
||||
@@ -295,12 +269,23 @@ public class Connection {
|
||||
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 APIRequestContextImpl(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 "LocalUtils":
|
||||
localUtils = new LocalUtils(parent, type, guid, initializer);
|
||||
result = localUtils;
|
||||
break;
|
||||
case "Page":
|
||||
result = new PageImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
@@ -322,12 +307,18 @@ public class Connection {
|
||||
case "Selectors":
|
||||
result = new SelectorsImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Tracing":
|
||||
result = new TracingImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "WebSocket":
|
||||
result = new WebSocketImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Worker":
|
||||
result = new WorkerImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "WritableStream":
|
||||
result = new WritableStream(parent, type, guid, initializer);
|
||||
break;
|
||||
default:
|
||||
throw new PlaywrightException("Unknown type " + type);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.microsoft.playwright.Page;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class DownloadImpl extends LoggingSupport implements Download {
|
||||
class DownloadImpl implements Download {
|
||||
private final PageImpl page;
|
||||
private final ArtifactImpl artifact;
|
||||
private final JsonObject initializer;
|
||||
@@ -44,19 +44,24 @@ class DownloadImpl extends LoggingSupport 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", () -> artifact.createReadStream());
|
||||
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
withLogging("Download.delete", () -> artifact.delete());
|
||||
page.withLogging("Download.delete", () -> artifact.delete());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String failure() {
|
||||
return withLogging("Download.failure", () -> artifact.failure());
|
||||
return page.withLogging("Download.failure", () -> artifact.failure());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,11 +71,11 @@ class DownloadImpl extends LoggingSupport implements Download {
|
||||
|
||||
@Override
|
||||
public Path path() {
|
||||
return withLogging("Download.path", () -> artifact.pathAfterFinished());
|
||||
return page.withLogging("Download.path", () -> artifact.pathAfterFinished());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAs(Path path) {
|
||||
withLogging("Download.saveAs", () -> artifact.saveAs(path));
|
||||
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.ElementHandle;
|
||||
import com.microsoft.playwright.FileChooser;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import com.microsoft.playwright.options.ElementState;
|
||||
@@ -34,6 +33,8 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
|
||||
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", () -> {
|
||||
@@ -280,7 +300,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame ownerFrame() {
|
||||
public FrameImpl ownerFrame() {
|
||||
return withLogging("ElementHandle.ownerFrame", () -> {
|
||||
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
|
||||
if (!json.has("frame")) {
|
||||
@@ -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(convertType(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheck(convertType(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path files, SetInputFilesOptions options) {
|
||||
setInputFiles(new Path[]{files}, options);
|
||||
@@ -427,7 +456,24 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(Utils.toFilePayloads(files), options);
|
||||
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
|
||||
FrameImpl frame = ownerFrame();
|
||||
if (frame == null) {
|
||||
throw new Error("Cannot set input files to detached element");
|
||||
}
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, frame.page().context());
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -441,6 +487,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.microsoft.playwright.options.FilePayload;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class FileChooserImpl implements FileChooser {
|
||||
private final PageImpl page;
|
||||
@@ -58,7 +58,8 @@ class FileChooserImpl implements FileChooser {
|
||||
|
||||
@Override
|
||||
public void setFiles(Path[] files, SetFilesOptions options) {
|
||||
setFiles(Utils.toFilePayloads(files), options);
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +70,6 @@ class FileChooserImpl implements FileChooser {
|
||||
@Override
|
||||
public void setFiles(FilePayload[] files, SetFilesOptions options) {
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.FilePayload;
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FormDataImpl implements FormData {
|
||||
Map<String, Object> fields = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public FormData set(String name, String value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, boolean value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, int value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, Path value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormData set(String name, FilePayload value) {
|
||||
fields.put(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -31,16 +31,17 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.options.LoadState.*;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.options.WaitUntilState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
|
||||
public class FrameImpl extends ChannelOwner implements Frame {
|
||||
private String name;
|
||||
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;
|
||||
@@ -60,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");
|
||||
@@ -133,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)));
|
||||
@@ -350,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());
|
||||
@@ -407,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));
|
||||
@@ -437,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));
|
||||
@@ -522,6 +565,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(this, selector, convertType(options, Locator.LocatorOptions.class));
|
||||
}
|
||||
|
||||
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
@@ -538,7 +586,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page page() {
|
||||
public PageImpl page() {
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -601,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, convertType(options, CheckOptions.class));
|
||||
} else {
|
||||
uncheckImpl(selector, convertType(options, UncheckOptions.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Frame.setContent", () -> setContentImpl(html, options));
|
||||
@@ -625,21 +686,32 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, page.context());
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
@@ -739,10 +811,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", logger -> {
|
||||
waitForLoadStateImpl(state, options, logger);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
|
||||
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options, Logger logger) {
|
||||
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options, logger);
|
||||
}
|
||||
|
||||
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options, Logger logger) {
|
||||
if (options == null) {
|
||||
options = new WaitForLoadStateOptions();
|
||||
}
|
||||
@@ -751,18 +830,20 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
List<Waitable<Void>> waitables = new ArrayList<>();
|
||||
waitables.add(new WaitForLoadStateHelper(state));
|
||||
waitables.add(new WaitForLoadStateHelper(state, logger));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableTimeout(options.timeout));
|
||||
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 final Logger logger;
|
||||
private boolean isDone;
|
||||
|
||||
WaitForLoadStateHelper(LoadState state) {
|
||||
WaitForLoadStateHelper(WaitUntilState state, Logger logger) {
|
||||
expectedState = state;
|
||||
this.logger = logger;
|
||||
isDone = loadStates.contains(state);
|
||||
if (!isDone) {
|
||||
internalListeners.add(InternalEventType.LOADSTATE, this);
|
||||
@@ -770,7 +851,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(LoadState state) {
|
||||
public void accept(WaitUntilState state) {
|
||||
logger.log(" load state changed to " + state);
|
||||
if (expectedState.equals(state)) {
|
||||
isDone = true;
|
||||
dispose();
|
||||
@@ -793,21 +875,25 @@ 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 final Logger logger;
|
||||
private WaitForLoadStateHelper loadStateHelper;
|
||||
|
||||
private RequestImpl request;
|
||||
private RuntimeException exception;
|
||||
|
||||
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
|
||||
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState, Logger logger) {
|
||||
this.matcher = matcher;
|
||||
this.expectedLoadState = expectedLoadState;
|
||||
this.logger = logger;
|
||||
internalListeners.add(InternalEventType.NAVIGATED, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(JsonObject params) {
|
||||
if (!matcher.test(params.get("url").getAsString())) {
|
||||
String url = params.get("url").getAsString();
|
||||
logger.log(" navigated to " + url);
|
||||
if (!matcher.test(url)) {
|
||||
return;
|
||||
}
|
||||
if (params.has("error")) {
|
||||
@@ -819,7 +905,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
request = connection.getExistingObject(jsonReq.get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState);
|
||||
loadStateHelper = new WaitForLoadStateHelper(expectedLoadState, logger);
|
||||
}
|
||||
internalListeners.remove(InternalEventType.NAVIGATED, this);
|
||||
}
|
||||
@@ -858,14 +944,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
|
||||
return withWaitLogging("Frame.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options, null));
|
||||
}
|
||||
|
||||
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
|
||||
return waitForNavigationImpl(code, options, null);
|
||||
Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
|
||||
return waitForNavigationImpl(logger, code, options, null);
|
||||
}
|
||||
|
||||
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
|
||||
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
|
||||
if (options == null) {
|
||||
options = new WaitForNavigationOptions();
|
||||
}
|
||||
@@ -875,9 +961,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
List<Waitable<Response>> waitables = new ArrayList<>();
|
||||
if (matcher == null) {
|
||||
matcher = UrlMatcher.forOneOf(options.url);
|
||||
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
|
||||
}
|
||||
waitables.add(new WaitForNavigationHelper(matcher, convertViaJson(options.waitUntil, LoadState.class)));
|
||||
logger.log("waiting for navigation " + matcher);
|
||||
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
|
||||
waitables.add(page.createWaitForCloseHelper());
|
||||
waitables.add(page.createWaitableFrameDetach(this));
|
||||
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
|
||||
@@ -890,11 +977,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) {
|
||||
@@ -909,18 +1001,14 @@ 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(url), options);
|
||||
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -934,27 +1022,50 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
|
||||
withWaitLogging("Frame.waitForURL", logger -> {
|
||||
waitForURLImpl(logger, matcher, options);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
void waitForURLImpl(Logger logger, UrlMatcher matcher, WaitForURLOptions options) {
|
||||
logger.log("waiting for url " + matcher);
|
||||
if (options == null) {
|
||||
options = new WaitForURLOptions();
|
||||
}
|
||||
if (matcher.test(url())) {
|
||||
waitForLoadStateImpl(convertViaJson(options.waitUntil, LoadState.class),
|
||||
convertViaJson(options, WaitForLoadStateOptions.class));
|
||||
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class), logger);
|
||||
return;
|
||||
}
|
||||
waitForNavigationImpl(() -> {}, convertViaJson(options, WaitForNavigationOptions.class), matcher);
|
||||
waitForNavigationImpl(logger, () -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
|
||||
}
|
||||
|
||||
int queryCount(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
|
||||
return result.get("value").getAsInt();
|
||||
}
|
||||
|
||||
void highlightImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("highlight", params);
|
||||
}
|
||||
|
||||
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);
|
||||
if (parentFrame == null && page != null) {
|
||||
if (state == LOAD) {
|
||||
page.listeners.notify(PageImpl.EventType.LOAD, page);
|
||||
} else if (state == DOMCONTENTLOADED) {
|
||||
page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page);
|
||||
}
|
||||
}
|
||||
internalListeners.notify(InternalEventType.LOADSTATE, state);
|
||||
}
|
||||
JsonElement remove = params.get("remove");
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.FrameLocator;
|
||||
import com.microsoft.playwright.Locator;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
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 Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameLocator nth(int index) {
|
||||
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Route;
|
||||
import com.microsoft.playwright.options.HarNotFound;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
public class HARRouter {
|
||||
private final LocalUtils localUtils;
|
||||
private final HarNotFound defaultAction;
|
||||
private final String harId;
|
||||
|
||||
HARRouter(LocalUtils localUtils, Path harFile, HarNotFound defaultAction) {
|
||||
this.localUtils = localUtils;
|
||||
this.defaultAction = defaultAction;
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("file", harFile.toString());
|
||||
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject();
|
||||
if (json.has("error")) {
|
||||
throw new PlaywrightException(json.get("error").getAsString());
|
||||
}
|
||||
harId = json.get("harId").getAsString();
|
||||
}
|
||||
|
||||
void handle(Route route) {
|
||||
Request request = route.request();
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", harId);
|
||||
params.addProperty("url", request.url());
|
||||
params.addProperty("method", request.method());
|
||||
params.add("headers", gson().toJsonTree(request.headersArray()));
|
||||
if (request.postDataBuffer() != null) {
|
||||
String base64 = Base64.getEncoder().encodeToString(request.postDataBuffer());
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
params.addProperty("isNavigationRequest", request.isNavigationRequest());
|
||||
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject();
|
||||
|
||||
String action = response.get("action").getAsString();
|
||||
if ("redirect".equals(action)) {
|
||||
String redirectURL = response.get("redirectURL").getAsString();
|
||||
logApiIfEnabled("HAR: " + route.request().url() + " redirected to " + redirectURL);
|
||||
((RouteImpl) route).redirectNavigationRequest(redirectURL);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("fulfill".equals(action)) {
|
||||
int status = response.get("status").getAsInt();
|
||||
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
|
||||
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(status)
|
||||
.setHeaders(headers)
|
||||
.setBodyBytes(buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
if ("error".equals(action)) {
|
||||
logApiIfEnabled("HAR: " + response.get("message").getAsString());
|
||||
// Report the error, but fall through to the default handler.
|
||||
}
|
||||
|
||||
if (defaultAction == HarNotFound.FALLBACK) {
|
||||
route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
// By default abort not matching requests.
|
||||
route.abort();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", harId);
|
||||
localUtils.sendMessageAsync("harClose", params);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+11
-19
@@ -16,28 +16,20 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import java.util.function.Function;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
class WaitableAdapter<F, T> implements Waitable<T> {
|
||||
private final Waitable<F> waitable;
|
||||
private final Function<F, T> transformation;
|
||||
import java.nio.file.Path;
|
||||
|
||||
WaitableAdapter(Waitable<F> waitable, Function<F, T> transformation) {
|
||||
this.waitable = waitable;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return waitable.isDone();
|
||||
class LocalUtils extends ChannelOwner {
|
||||
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return transformation.apply(waitable.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
waitable.dispose();
|
||||
void zip(Path zipFile, JsonArray entries) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("zipFile", zipFile.toString());
|
||||
params.add("entries", entries);
|
||||
sendMessage("zip", params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* 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.lang.reflect.Field;
|
||||
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.convertType;
|
||||
|
||||
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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsText(Pattern pattern, ContainsTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(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 = convertType(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", convertType(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", convertType(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", convertType(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", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasCount(int count, HasCountOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasCountOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertType(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 = convertType(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", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasId(Pattern pattern, HasIdOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasJSProperty(String name, Object value, HasJSPropertyOptions options) {
|
||||
if (options == null) {
|
||||
options = new HasJSPropertyOptions();
|
||||
}
|
||||
FrameExpectOptions commonOptions = convertType(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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasText(Pattern pattern, HasTextOptions options) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.ignoreCase = shouldIgnoreCase(options);
|
||||
// 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", convertType(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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = false;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(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.ignoreCase = shouldIgnoreCase(options);
|
||||
expected.matchSubstring = true;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(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", convertType(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", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValues(String[] values, HasValuesOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (String text : values) {
|
||||
ExpectedTextValue expected = new ExpectedTextValue();
|
||||
expected.string = text;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasValues(Pattern[] patterns, HasValuesOptions options) {
|
||||
List<ExpectedTextValue> list = new ArrayList<>();
|
||||
for (Pattern pattern : patterns) {
|
||||
ExpectedTextValue expected = expectedRegex(pattern);
|
||||
expected.matchSubstring = true;
|
||||
list.add(expected);
|
||||
}
|
||||
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isChecked(IsCheckedOptions options) {
|
||||
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
|
||||
expectTrue(expression, "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isDisabled(IsDisabledOptions options) {
|
||||
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEditable(IsEditableOptions options) {
|
||||
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
|
||||
boolean editable = options == null || options.editable == null || options.editable == true;
|
||||
expectTrue(editable ? "to.be.editable" : "to.be.readonly", "Locator expected to be editable", frameOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEmpty(IsEmptyOptions options) {
|
||||
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isEnabled(IsEnabledOptions options) {
|
||||
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
|
||||
boolean enabled = options == null || options.enabled == null || options.enabled == true;
|
||||
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", "Locator expected to be enabled", frameOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isFocused(IsFocusedOptions options) {
|
||||
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isHidden(IsHiddenOptions options) {
|
||||
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isVisible(IsVisibleOptions options) {
|
||||
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
|
||||
boolean visible = options == null || options.visible == null || options.visible == true;
|
||||
expectTrue(visible ? "to.be.visible" : "to.be.hidden", "Locator expected to be visible", frameOptions);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static Boolean shouldIgnoreCase(Object options) {
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Field fromField = options.getClass().getDeclaredField("ignoreCase");
|
||||
Object value = fromField.get(options);
|
||||
return (Boolean) value;
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
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.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
|
||||
|
||||
class LocatorImpl implements Locator {
|
||||
private final FrameImpl frame;
|
||||
private final String selector;
|
||||
|
||||
private static class Filters {
|
||||
private final Map<Field, String> filterFieldToEngine = new LinkedHashMap<>();
|
||||
private void addFilter(String name, String engine) throws NoSuchFieldException {
|
||||
filterFieldToEngine.put(LocatorOptions.class.getField(name), engine);
|
||||
}
|
||||
{
|
||||
try {
|
||||
addFilter("has", "has");
|
||||
// addFilter("leftOf", "left-of");
|
||||
// addFilter("rightOf", "right-of");
|
||||
// addFilter("above", "above");
|
||||
// addFilter("below", "below");
|
||||
// addFilter("near", "near");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
String addFiltersToSelector(String selector, LocatorOptions options, Frame frame) {
|
||||
try {
|
||||
for (Map.Entry<Field, String> p : filterFieldToEngine.entrySet()) {
|
||||
LocatorImpl filter = (LocatorImpl) p.getKey().get(options);
|
||||
if (filter == null) {
|
||||
continue;
|
||||
}
|
||||
if (filter.frame != frame) {
|
||||
throw new PlaywrightException("Inner '" + p.getKey().getName() + "' locator must belong to the same frame.");
|
||||
}
|
||||
selector += " >> " + p.getValue() + "=" + gson().toJson(filter.selector);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new PlaywrightException("Unexpected options", e);
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
private static final Filters filters = new Filters();
|
||||
|
||||
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
|
||||
this.frame = frame;
|
||||
if (options != null) {
|
||||
if (options.hasText != null) {
|
||||
if (options.hasText instanceof Pattern) {
|
||||
Pattern pattern = (Pattern) options.hasText;
|
||||
String jsRegex = "/" + pattern.pattern() + "/" + toJsRegexFlags(pattern);
|
||||
selector += " >> has=" + gson().toJson("text=" + jsRegex);
|
||||
} else if (options.hasText instanceof String) {
|
||||
String text = (String) options.hasText;
|
||||
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
|
||||
}
|
||||
}
|
||||
selector = filters.addFiltersToSelector(selector, options, frame);
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
private static String escapeWithQuotes(String text) {
|
||||
return gson().toJson(text);
|
||||
}
|
||||
|
||||
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
|
||||
ElementHandleOptions handleOptions = convertType(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, convertType(options, Frame.CheckOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(ClickOptions options) {
|
||||
if (options == null) {
|
||||
options = new ClickOptions();
|
||||
}
|
||||
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return frame.queryCount(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dblclick(DblclickOptions options) {
|
||||
if (options == null) {
|
||||
options = new DblclickOptions();
|
||||
}
|
||||
frame.dblclick(selector, convertType(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, convertType(options, Frame.DispatchEventOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragTo(Locator target, DragToOptions options) {
|
||||
if (options == null) {
|
||||
options = new DragToOptions();
|
||||
}
|
||||
Frame.DragAndDropOptions frameOptions = convertType(options, Frame.DragAndDropOptions.class);
|
||||
frameOptions.setStrict(true);
|
||||
frame.dragAndDrop(selector, ((LocatorImpl) target).selector, frameOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle elementHandle(ElementHandleOptions options) {
|
||||
if (options == null) {
|
||||
options = new ElementHandleOptions();
|
||||
}
|
||||
Frame.WaitForSelectorOptions frameOptions = convertType(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, convertType(options, Frame.FillOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator filter(FilterOptions options) {
|
||||
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator first() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=0", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus(FocusOptions options) {
|
||||
if (options == null) {
|
||||
options = new FocusOptions();
|
||||
}
|
||||
frame.focus(selector, convertType(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, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void highlight() {
|
||||
frame.highlightImpl(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hover(HoverOptions options) {
|
||||
if (options == null) {
|
||||
options = new HoverOptions();
|
||||
}
|
||||
frame.hover(selector, convertType(options, Frame.HoverOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(InnerHTMLOptions options) {
|
||||
if (options == null) {
|
||||
options = new InnerHTMLOptions();
|
||||
}
|
||||
return frame.innerHTML(selector, convertType(options, Frame.InnerHTMLOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerText(InnerTextOptions options) {
|
||||
if (options == null) {
|
||||
options = new InnerTextOptions();
|
||||
}
|
||||
return frame.innerText(selector, convertType(options, Frame.InnerTextOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(InputValueOptions options) {
|
||||
if (options == null) {
|
||||
options = new InputValueOptions();
|
||||
}
|
||||
return frame.inputValue(selector, convertType(options, Frame.InputValueOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(IsCheckedOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsCheckedOptions();
|
||||
}
|
||||
return frame.isChecked(selector, convertType(options, Frame.IsCheckedOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled(IsDisabledOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsDisabledOptions();
|
||||
}
|
||||
return frame.isDisabled(selector, convertType(options, Frame.IsDisabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(IsEditableOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsEditableOptions();
|
||||
}
|
||||
return frame.isEditable(selector, convertType(options, Frame.IsEditableOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(IsEnabledOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsEnabledOptions();
|
||||
}
|
||||
return frame.isEnabled(selector, convertType(options, Frame.IsEnabledOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden(IsHiddenOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsHiddenOptions();
|
||||
}
|
||||
return frame.isHidden(selector, convertType(options, Frame.IsHiddenOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible(IsVisibleOptions options) {
|
||||
if (options == null) {
|
||||
options = new IsVisibleOptions();
|
||||
}
|
||||
return frame.isVisible(selector, convertType(options, Frame.IsVisibleOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator last() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=-1", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return new LocatorImpl(frame, this.selector + " >> " + selector, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator nth(int index) {
|
||||
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page page() {
|
||||
return frame.page();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void press(String key, PressOptions options) {
|
||||
if (options == null) {
|
||||
options = new PressOptions();
|
||||
}
|
||||
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] screenshot(ScreenshotOptions options) {
|
||||
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
|
||||
withElement((h, o) -> {
|
||||
h.scrollIntoViewIfNeeded(o);
|
||||
return null;
|
||||
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String values, SelectOptionOptions options) {
|
||||
if (options == null) {
|
||||
options = new SelectOptionOptions();
|
||||
}
|
||||
return frame.selectOption(selector, values, convertType(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, convertType(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, convertType(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, convertType(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, convertType(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, convertType(options, Frame.SelectOptionOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectText(SelectTextOptions options) {
|
||||
withElement((h, o) -> {
|
||||
h.selectText(o);
|
||||
return null;
|
||||
}, convertType(options, ElementHandle.SelectTextOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked, SetCheckedOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetCheckedOptions();
|
||||
}
|
||||
frame.setChecked(selector, checked, convertType(options, Frame.SetCheckedOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
frame.setInputFiles(selector, files, convertType(options, Frame.SetInputFilesOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tap(TapOptions options) {
|
||||
if (options == null) {
|
||||
options = new TapOptions();
|
||||
}
|
||||
frame.tap(selector, convertType(options, Frame.TapOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textContent(TextContentOptions options) {
|
||||
if (options == null) {
|
||||
options = new TextContentOptions();
|
||||
}
|
||||
return frame.textContent(selector, convertType(options, Frame.TextContentOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void type(String text, TypeOptions options) {
|
||||
if (options == null) {
|
||||
options = new TypeOptions();
|
||||
}
|
||||
frame.type(selector, text, convertType(options, Frame.TypeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncheck(UncheckOptions options) {
|
||||
if (options == null) {
|
||||
options = new UncheckOptions();
|
||||
}
|
||||
frame.uncheck(selector, convertType(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, convertType(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));
|
||||
}
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject result = new JsonObject();
|
||||
result.add("frame", frame.toProtocolRef());
|
||||
result.addProperty("selector", selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
interface Logger {
|
||||
void log(String message);
|
||||
}
|
||||
@@ -60,7 +60,13 @@ class LoggingSupport {
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
|
||||
private void logApi(String message) {
|
||||
static void logApiIfEnabled(String message) {
|
||||
if (isEnabled) {
|
||||
logApi(message);
|
||||
}
|
||||
}
|
||||
|
||||
static void logApi(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:api " + message);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Mouse;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
class MouseImpl implements Mouse {
|
||||
private final ChannelOwner page;
|
||||
@@ -54,7 +54,7 @@ class MouseImpl implements Mouse {
|
||||
if (options == null) {
|
||||
clickOptions = new ClickOptions();
|
||||
} else {
|
||||
clickOptions = convertViaJson(options, ClickOptions.class);
|
||||
clickOptions = convertType(options, ClickOptions.class);
|
||||
}
|
||||
clickOptions.clickCount = 2;
|
||||
click(x, y, clickOptions);
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.convertType;
|
||||
|
||||
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;
|
||||
expected.normalizeWhiteSpace = true;
|
||||
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(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", convertType(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", convertType(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", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAssertions not() {
|
||||
return new PageAssertionsImpl(actualPage, !isNot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertViaJson;
|
||||
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;
|
||||
@@ -125,7 +125,14 @@ 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 ("worker".equals(event)) {
|
||||
String guid = params.getAsJsonObject("worker").get("guid").getAsString();
|
||||
@@ -163,13 +170,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
try {
|
||||
bindingCall.call(binding);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
if (!isSafeCloseError(e.getMessage())) {
|
||||
logWithTimestamp(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("load".equals(event)) {
|
||||
listeners.notify(EventType.LOAD, this);
|
||||
} else if ("domcontentloaded".equals(event)) {
|
||||
listeners.notify(EventType.DOMCONTENTLOADED, this);
|
||||
} else if ("frameAttached".equals(event)) {
|
||||
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
|
||||
FrameImpl frame = connection.getExistingObject(guid);
|
||||
@@ -189,13 +194,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
listeners.notify(EventType.FRAMEDETACHED, frame);
|
||||
} 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);
|
||||
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
|
||||
Router.HandleResult handled = routes.handle(route);
|
||||
if (handled == Router.HandleResult.FoundMatchingHandler) {
|
||||
maybeDisableNetworkInterception();
|
||||
}
|
||||
if (!handled) {
|
||||
route.resume();
|
||||
if (!route.isHandled()) {
|
||||
browserContext.handleRoute(route);
|
||||
}
|
||||
} else if ("video".equals(event)) {
|
||||
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
|
||||
@@ -438,43 +443,43 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForClose", () -> waitForCloseImpl(options, code));
|
||||
return withWaitLogging("Page.waitForClose", logger -> 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));
|
||||
return withWaitLogging("Page.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
|
||||
}
|
||||
|
||||
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
|
||||
if (options == null) {
|
||||
options = new WaitForConsoleMessageOptions();
|
||||
}
|
||||
return waitForEventWithTimeout(EventType.CONSOLE, code, options.timeout);
|
||||
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Download waitForDownload(WaitForDownloadOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForDownload", () -> waitForDownloadImpl(options, code));
|
||||
return withWaitLogging("Page.waitForDownload", logger -> 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));
|
||||
return withWaitLogging("Page.waitForFileChooser", logger -> waitForFileChooserImpl(options, code));
|
||||
}
|
||||
|
||||
private FileChooser waitForFileChooserImpl(WaitForFileChooserOptions options, Runnable code) {
|
||||
@@ -482,48 +487,48 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
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));
|
||||
return withWaitLogging("Page.waitForPopup", logger -> 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));
|
||||
return withWaitLogging("Page.waitForWebSocket", logger -> 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));
|
||||
return withWaitLogging("Page.waitForWorker", logger -> 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, Double 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));
|
||||
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
|
||||
waitables.add(createWaitForCloseHelper());
|
||||
waitables.add(createWaitableTimeout(timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
@@ -548,8 +553,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, convertType(options, Frame.QuerySelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -558,8 +564,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, convertType(options, Frame.EvalOnSelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -593,13 +600,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public ElementHandle addScriptTag(AddScriptTagOptions options) {
|
||||
return withLogging("Page.addScriptTag",
|
||||
() -> mainFrame.addScriptTagImpl(convertViaJson(options, Frame.AddScriptTagOptions.class)));
|
||||
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementHandle addStyleTag(AddStyleTagOptions options) {
|
||||
return withLogging("Page.addStyleTag",
|
||||
() -> mainFrame.addStyleTagImpl(convertViaJson(options, Frame.AddStyleTagOptions.class)));
|
||||
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -610,13 +617,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void check(String selector, CheckOptions options) {
|
||||
withLogging("Page.check",
|
||||
() -> mainFrame.checkImpl(selector, convertViaJson(options, Frame.CheckOptions.class)));
|
||||
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(String selector, ClickOptions options) {
|
||||
withLogging("Page.click",
|
||||
() -> mainFrame.clickImpl(selector, convertViaJson(options, Frame.ClickOptions.class)));
|
||||
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -632,13 +639,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void dblclick(String selector, DblclickOptions options) {
|
||||
withLogging("Page.dblclick",
|
||||
() -> mainFrame.dblclickImpl(selector, convertViaJson(options, Frame.DblclickOptions.class)));
|
||||
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
|
||||
withLogging("Page.dispatchEvent",
|
||||
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class)));
|
||||
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -695,13 +702,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void fill(String selector, String value, FillOptions options) {
|
||||
withLogging("Page.fill",
|
||||
() -> mainFrame.fillImpl(selector, value, convertViaJson(options, Frame.FillOptions.class)));
|
||||
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus(String selector, FocusOptions options) {
|
||||
withLogging("Page.focus",
|
||||
() -> mainFrame.focusImpl(selector, convertViaJson(options, Frame.FocusOptions.class)));
|
||||
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -716,7 +723,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
|
||||
@@ -729,6 +736,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())) {
|
||||
@@ -746,7 +758,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public String getAttribute(String selector, String name, GetAttributeOptions options) {
|
||||
return withLogging("Page.getAttribute",
|
||||
() -> mainFrame.getAttributeImpl(selector, name, convertViaJson(options, Frame.GetAttributeOptions.class)));
|
||||
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -785,32 +797,41 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public ResponseImpl navigate(String url, NavigateOptions options) {
|
||||
return withLogging("Page.navigate", () ->
|
||||
mainFrame.navigateImpl(url, convertViaJson(options, Frame.NavigateOptions.class)));
|
||||
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hover(String selector, HoverOptions options) {
|
||||
withLogging("Page.hover", () ->
|
||||
mainFrame.hoverImpl(selector, convertViaJson(options, Frame.HoverOptions.class)));
|
||||
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
|
||||
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerHTML(String selector, InnerHTMLOptions options) {
|
||||
return withLogging("Page.innerHTML",
|
||||
() -> mainFrame.innerHTMLImpl(selector, convertViaJson(options, Frame.InnerHTMLOptions.class)));
|
||||
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String innerText(String selector, InnerTextOptions options) {
|
||||
return withLogging("Page.innerText",
|
||||
() -> mainFrame.innerTextImpl(selector, convertViaJson(options, Frame.InnerTextOptions.class)));
|
||||
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputValue(String selector, InputValueOptions options) {
|
||||
return withLogging("Page.inputValue",
|
||||
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked(String selector, IsCheckedOptions options) {
|
||||
return withLogging("Page.isChecked",
|
||||
() -> mainFrame.isCheckedImpl(selector, convertViaJson(options, Frame.IsCheckedOptions.class)));
|
||||
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -821,31 +842,31 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public boolean isDisabled(String selector, IsDisabledOptions options) {
|
||||
return withLogging("Page.isDisabled",
|
||||
() -> mainFrame.isDisabledImpl(selector, convertViaJson(options, Frame.IsDisabledOptions.class)));
|
||||
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(String selector, IsEditableOptions options) {
|
||||
return withLogging("Page.isEditable",
|
||||
() -> mainFrame.isEditableImpl(selector, convertViaJson(options, Frame.IsEditableOptions.class)));
|
||||
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(String selector, IsEnabledOptions options) {
|
||||
return withLogging("Page.isEnabled",
|
||||
() -> mainFrame.isEnabledImpl(selector, convertViaJson(options, Frame.IsEnabledOptions.class)));
|
||||
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden(String selector, IsHiddenOptions options) {
|
||||
return withLogging("Page.isHidden",
|
||||
() -> mainFrame.isHiddenImpl(selector, convertViaJson(options, Frame.IsHiddenOptions.class)));
|
||||
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible(String selector, IsVisibleOptions options) {
|
||||
return withLogging("Page.isVisible",
|
||||
() -> mainFrame.isVisibleImpl(selector, convertViaJson(options, Frame.IsVisibleOptions.class)));
|
||||
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -853,6 +874,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator locator(String selector, LocatorOptions options) {
|
||||
return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame mainFrame() {
|
||||
return mainFrame;
|
||||
@@ -903,7 +929,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void press(String selector, String key, PressOptions options) {
|
||||
withLogging("Page.press",
|
||||
() -> mainFrame.pressImpl(selector, key, convertViaJson(options, Frame.PressOptions.class)));
|
||||
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -911,6 +937,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return withLogging("Page.reload", () -> reloadImpl(options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequestContextImpl request() {
|
||||
return browserContext.request();
|
||||
}
|
||||
|
||||
private Response reloadImpl(ReloadOptions options) {
|
||||
if (options == null) {
|
||||
options = new ReloadOptions();
|
||||
@@ -924,23 +955,38 @@ 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) {
|
||||
@Override
|
||||
public void routeFromHAR(Path har, RouteFromHAROptions options) {
|
||||
if (options == null) {
|
||||
options = new RouteFromHAROptions();
|
||||
}
|
||||
if (options.update != null && options.update) {
|
||||
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
|
||||
return;
|
||||
}
|
||||
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url);
|
||||
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
|
||||
onClose(context -> harRouter.dispose());
|
||||
route(matcher, route -> harRouter.handle(route), null);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1000,8 +1046,18 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Locator> mask = options.mask;
|
||||
options.mask = null;
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
options.mask = mask;
|
||||
params.remove("path");
|
||||
if (mask != null) {
|
||||
JsonArray maskArray = new JsonArray();
|
||||
for (Locator locator: mask) {
|
||||
maskArray.add(((LocatorImpl) locator).toProtocol());
|
||||
}
|
||||
params.add("mask", maskArray);
|
||||
}
|
||||
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
|
||||
|
||||
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
@@ -1014,19 +1070,25 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
|
||||
return withLogging("Page.selectOption",
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
|
||||
return withLogging("Page.selectOption",
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)));
|
||||
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
|
||||
withLogging("Page.setChecked",
|
||||
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(String html, SetContentOptions options) {
|
||||
withLogging("Page.setContent",
|
||||
() -> mainFrame.setContentImpl(html, convertViaJson(options, Frame.SetContentOptions.class)));
|
||||
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1073,7 +1135,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
withLogging("Page.setInputFiles",
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1084,7 +1146,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Page.setInputFiles",
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertViaJson(options, Frame.SetInputFilesOptions.class)));
|
||||
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1100,13 +1162,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void tap(String selector, TapOptions options) {
|
||||
withLogging("Page.tap",
|
||||
() -> mainFrame.tapImpl(selector, convertViaJson(options, Frame.TapOptions.class)));
|
||||
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String textContent(String selector, TextContentOptions options) {
|
||||
return withLogging("Page.textContent",
|
||||
() -> mainFrame.textContentImpl(selector, convertViaJson(options, Frame.TextContentOptions.class)));
|
||||
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1122,18 +1184,18 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public void type(String selector, String text, TypeOptions options) {
|
||||
withLogging("Page.type",
|
||||
() -> mainFrame.typeImpl(selector, text, convertViaJson(options, Frame.TypeOptions.class)));
|
||||
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncheck(String selector, UncheckOptions options) {
|
||||
withLogging("Page.uncheck",
|
||||
() -> mainFrame.uncheckImpl(selector, convertViaJson(options, Frame.UncheckOptions.class)));
|
||||
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unroute(String url, Consumer<Route> handler) {
|
||||
unroute(new UrlMatcher(url), handler);
|
||||
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1149,14 +1211,18 @@ 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();
|
||||
@@ -1197,28 +1263,30 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
@Override
|
||||
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
|
||||
return withLogging("Page.waitForFunction",
|
||||
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertViaJson(options, Frame.WaitForFunctionOptions.class)));
|
||||
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||
withLogging("Page.waitForLoadState",
|
||||
() -> mainFrame.waitForLoadStateImpl(state, convertViaJson(options, Frame.WaitForLoadStateOptions.class)));
|
||||
withWaitLogging("Page.waitForLoadState", logger -> {
|
||||
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
|
||||
return withLogging("Page.waitForNavigation", () -> waitForNavigationImpl(code, options));
|
||||
return withWaitLogging("Page.waitForNavigation", logger -> waitForNavigationImpl(logger, code, options));
|
||||
}
|
||||
|
||||
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
|
||||
private Response waitForNavigationImpl(Logger logger, Runnable code, WaitForNavigationOptions options) {
|
||||
Frame.WaitForNavigationOptions frameOptions = new Frame.WaitForNavigationOptions();
|
||||
if (options != null) {
|
||||
frameOptions.timeout = options.timeout;
|
||||
frameOptions.waitUntil = options.waitUntil;
|
||||
frameOptions.url = options.url;
|
||||
}
|
||||
return mainFrame.waitForNavigationImpl(code, frameOptions);
|
||||
return mainFrame.waitForNavigationImpl(logger, code, frameOptions);
|
||||
}
|
||||
|
||||
void frameNavigated(FrameImpl frame) {
|
||||
@@ -1270,88 +1338,86 @@ 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(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(Pattern urlPattern, WaitForRequestOptions options, Runnable code) {
|
||||
return waitForRequest(toRequestPredicate(new UrlMatcher(urlPattern)), options, code);
|
||||
return waitForRequest(new UrlMatcher(urlPattern), null, options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request waitForRequest(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForRequest", () -> waitForRequestImpl(predicate, options, code));
|
||||
return waitForRequest(null, predicate, options, code);
|
||||
}
|
||||
|
||||
private static Predicate<Request> toRequestPredicate(UrlMatcher matcher) {
|
||||
return request -> matcher.test(request.url());
|
||||
private Request waitForRequest(UrlMatcher urlMatcher, Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForRequest", logger -> {
|
||||
logger.log("waiting for request " + ((urlMatcher == null) ? "matching predicate" : urlMatcher.toString()));
|
||||
Predicate<Request> requestPredicate = predicate;
|
||||
if (requestPredicate == null) {
|
||||
requestPredicate = request -> urlMatcher.test(request.url());;
|
||||
}
|
||||
return waitForRequestImpl(requestPredicate, options, code);
|
||||
});
|
||||
}
|
||||
|
||||
private Request waitForRequestImpl(Predicate<Request> predicate, WaitForRequestOptions options, Runnable code) {
|
||||
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));
|
||||
return withWaitLogging("Page.waitForRequestFinished", logger -> waitForRequestFinishedImpl(options, code));
|
||||
}
|
||||
|
||||
private Request waitForRequestFinishedImpl(WaitForRequestFinishedOptions options, Runnable code) {
|
||||
if (options == null) {
|
||||
options = new WaitForRequestFinishedOptions();
|
||||
}
|
||||
List<Waitable<Request>> waitables = new ArrayList<>();
|
||||
Predicate<Request> predicate = options.predicate;
|
||||
waitables.add(new WaitableEvent<>(listeners, EventType.REQUESTFINISHED,
|
||||
request -> predicate == null || predicate.test(request)));
|
||||
waitables.add(createWaitForCloseHelper());
|
||||
waitables.add(createWaitableTimeout(options.timeout));
|
||||
return runUntil(code, new WaitableRace<>(waitables));
|
||||
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(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForResponse(Pattern urlPattern, WaitForResponseOptions options, Runnable code) {
|
||||
return waitForResponse(toResponsePredicate(new UrlMatcher(urlPattern)), options, code);
|
||||
return waitForResponse(new UrlMatcher(urlPattern), null, options, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response waitForResponse(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
|
||||
return withLogging("Page.waitForResponse", () -> waitForResponseImpl(predicate, options, code));
|
||||
return waitForResponse(null, predicate, options, code);
|
||||
}
|
||||
|
||||
private static Predicate<Response> toResponsePredicate(UrlMatcher matcher) {
|
||||
return response -> matcher.test(response.url());
|
||||
private Response waitForResponse(UrlMatcher urlMatcher, Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
|
||||
return withWaitLogging("Page.waitForResponse", logger -> {
|
||||
logger.log("waiting for response " + ((urlMatcher == null) ? "matching predicate" : urlMatcher.toString()));
|
||||
Predicate<Response> responsePredicate = predicate;
|
||||
if (responsePredicate == null) {
|
||||
responsePredicate = response -> urlMatcher.test(response.url());;
|
||||
}
|
||||
return waitForResponseImpl(responsePredicate, options, code);
|
||||
});
|
||||
}
|
||||
|
||||
private Response waitForResponseImpl(Predicate<Response> predicate, WaitForResponseOptions options, Runnable code) {
|
||||
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
|
||||
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
|
||||
return withLogging("Page.waitForSelector",
|
||||
() -> mainFrame.waitForSelectorImpl(selector, convertViaJson(options, Frame.WaitForSelectorOptions.class)));
|
||||
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1361,7 +1427,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public void waitForURL(String url, WaitForURLOptions options) {
|
||||
waitForURL(new UrlMatcher(url), options);
|
||||
waitForURL(new UrlMatcher(browserContext.baseUrl, url), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1375,7 +1441,10 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
|
||||
withLogging("Page.waitForURL", () -> mainFrame.waitForURLImpl(matcher, convertViaJson(options, Frame.WaitForURLOptions.class)));
|
||||
withWaitLogging("Page.waitForURL", logger -> {
|
||||
mainFrame.waitForURLImpl(logger, matcher, convertType(options, Frame.WaitForURLOptions.class));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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,27 @@ 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 {
|
||||
String message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
JsonObject message = incoming.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
if (message == null && readerThread.exception != null) {
|
||||
try {
|
||||
close();
|
||||
@@ -91,7 +97,7 @@ 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;
|
||||
|
||||
@@ -107,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;
|
||||
}
|
||||
@@ -116,7 +122,8 @@ 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) {
|
||||
exception = e;
|
||||
|
||||
@@ -17,26 +17,39 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.APIRequest;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Selectors;
|
||||
import com.microsoft.playwright.impl.driver.Driver;
|
||||
|
||||
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) {
|
||||
return createImpl(options, false);
|
||||
}
|
||||
|
||||
public static PlaywrightImpl createImpl(CreateOptions options, boolean forceNewDriverInstanceForTests) {
|
||||
Map<String, String> env = Collections.emptyMap();
|
||||
if (options != null && options.env != null) {
|
||||
env = options.env;
|
||||
}
|
||||
Driver driver = forceNewDriverInstanceForTests ?
|
||||
Driver.createAndInstall(env, true) :
|
||||
Driver.ensureDriverInstalled(env, true);
|
||||
try {
|
||||
Path driver = Driver.ensureDriverInstalled();
|
||||
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "run-driver");
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().add("run-driver");
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
// pb.environment().put("DEBUG", "pw:pro*");
|
||||
Process p = pb.start();
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()));
|
||||
PlaywrightImpl result = (PlaywrightImpl) connection.waitForObjectWithKnownName("Playwright");
|
||||
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
|
||||
PlaywrightImpl result = connection.initializePlaywright();
|
||||
result.driverProcess = p;
|
||||
result.initSharedSelectors(null);
|
||||
return result;
|
||||
@@ -49,7 +62,9 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private final BrowserTypeImpl firefox;
|
||||
private final BrowserTypeImpl webkit;
|
||||
private final SelectorsImpl selectors;
|
||||
private SharedSelectors sharedSelectors;;
|
||||
private final APIRequestImpl apiRequest;
|
||||
private final LocalUtils localUtils;
|
||||
private SharedSelectors sharedSelectors;
|
||||
|
||||
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -58,12 +73,17 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
|
||||
|
||||
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
apiRequest = new APIRequestImpl(this);
|
||||
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
|
||||
chromium.localUtils = localUtils;
|
||||
firefox.localUtils = localUtils;
|
||||
webkit.localUtils = localUtils;
|
||||
}
|
||||
|
||||
void initSharedSelectors(PlaywrightImpl parent) {
|
||||
assert sharedSelectors == null;
|
||||
if (parent == null) {
|
||||
sharedSelectors = new SharedSelectors();;
|
||||
sharedSelectors = new SharedSelectors();
|
||||
} else {
|
||||
sharedSelectors = parent.sharedSelectors;
|
||||
}
|
||||
@@ -84,6 +104,11 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
return firefox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIRequest request() {
|
||||
return apiRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserTypeImpl webkit() {
|
||||
return webkit;
|
||||
|
||||
@@ -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;
|
||||
@@ -37,6 +31,7 @@ class SerializedValue{
|
||||
// Possible values: { 'null, 'undefined, 'NaN, 'Infinity, '-Infinity, '-0 }
|
||||
String v;
|
||||
String d;
|
||||
String u;
|
||||
public static class R {
|
||||
String p;
|
||||
String f;
|
||||
@@ -49,47 +44,15 @@ class SerializedValue{
|
||||
}
|
||||
O[] o;
|
||||
Number h;
|
||||
Integer id;
|
||||
Integer ref;
|
||||
}
|
||||
|
||||
|
||||
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 +82,29 @@ class SerializedError{
|
||||
}
|
||||
}
|
||||
|
||||
class ExpectedTextValue {
|
||||
String string;
|
||||
String regexSource;
|
||||
String regexFlags;
|
||||
Boolean ignoreCase;
|
||||
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,40 @@
|
||||
|
||||
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;
|
||||
private FallbackOverrides fallbackOverrides;
|
||||
|
||||
static class FallbackOverrides {
|
||||
String url;
|
||||
String method;
|
||||
byte[] postData;
|
||||
Map<String, String> headers;
|
||||
}
|
||||
|
||||
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -43,10 +58,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 +66,37 @@ 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;
|
||||
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
|
||||
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers)).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
|
||||
@@ -76,19 +106,26 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
|
||||
@Override
|
||||
public String method() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.method != null) {
|
||||
return fallbackOverrides.method;
|
||||
}
|
||||
return initializer.get("method").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postData() {
|
||||
if (postData == null) {
|
||||
byte[] buffer = postDataBuffer();
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(postData, StandardCharsets.UTF_8);
|
||||
return new String(buffer, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] postDataBuffer() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.postData != null) {
|
||||
return fallbackOverrides.postData;
|
||||
}
|
||||
return postData;
|
||||
}
|
||||
|
||||
@@ -108,7 +145,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 +155,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;
|
||||
@@ -125,6 +174,9 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.url != null) {
|
||||
return fallbackOverrides.url;
|
||||
}
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
@@ -132,4 +184,42 @@ public class RequestImpl extends ChannelOwner implements Request {
|
||||
return redirectedTo != null ? redirectedTo.finalRequest() : this;
|
||||
}
|
||||
|
||||
private RawHeaders getRawHeaders() {
|
||||
if (fallbackOverrides != null && fallbackOverrides.headers != null) {
|
||||
return new RawHeaders(Utils.toHeadersList(fallbackOverrides.headers));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void applyFallbackOverrides(FallbackOverrides overrides) {
|
||||
if (fallbackOverrides == null) {
|
||||
fallbackOverrides = new FallbackOverrides();
|
||||
}
|
||||
if (overrides.url != null) {
|
||||
fallbackOverrides.url = overrides.url;
|
||||
}
|
||||
if (overrides.method != null) {
|
||||
fallbackOverrides.method = overrides.method;
|
||||
}
|
||||
if (overrides.headers != null) {
|
||||
fallbackOverrides.headers = overrides.headers;
|
||||
}
|
||||
if (overrides.postData != null) {
|
||||
fallbackOverrides.postData = overrides.postData;
|
||||
}
|
||||
}
|
||||
|
||||
FallbackOverrides fallbackOverridesForResume() {
|
||||
return fallbackOverrides;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.FormData;
|
||||
import com.microsoft.playwright.options.RequestOptions;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestOptionsImpl implements RequestOptions {
|
||||
Map<String, Object> params;
|
||||
String method;
|
||||
Map<String, String> headers;
|
||||
Object data;
|
||||
FormDataImpl form;
|
||||
FormDataImpl multipart;
|
||||
Boolean failOnStatusCode;
|
||||
Boolean ignoreHTTPSErrors;
|
||||
Double timeout;
|
||||
Integer maxRedirects;
|
||||
|
||||
@Override
|
||||
public RequestOptions setHeader(String name, String value) {
|
||||
if (headers == null) {
|
||||
headers = new LinkedHashMap<>();
|
||||
}
|
||||
headers.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(String data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setForm(FormData form) {
|
||||
this.form = (FormDataImpl) form;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMultipart(FormData form) {
|
||||
this.multipart = (FormDataImpl) form;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, String value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, boolean value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setQueryParam(String name, int value) {
|
||||
return setQueryParamImpl(name, value);
|
||||
}
|
||||
|
||||
private RequestOptions setQueryParamImpl(String name, Object value) {
|
||||
if (params == null) {
|
||||
params = new LinkedHashMap<>();
|
||||
}
|
||||
params.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setFailOnStatusCode(boolean failOnStatusCode) {
|
||||
this.failOnStatusCode = failOnStatusCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
|
||||
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestOptions setMaxRedirects(int maxRedirects) {
|
||||
this.maxRedirects = maxRedirects;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -73,9 +83,29 @@ public class ResponseImpl extends ChannelOwner implements Response {
|
||||
return request().frame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fromServiceWorker() {
|
||||
return initializer.get("fromServiceWorker").getAsBoolean();
|
||||
}
|
||||
|
||||
@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 +118,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 +159,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Frame;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Request;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -28,56 +28,93 @@ import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
|
||||
public class RouteImpl extends ChannelOwner implements Route {
|
||||
private final RequestImpl request;
|
||||
private boolean handled;
|
||||
|
||||
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort(String errorCode) {
|
||||
startHandling();
|
||||
withLogging("Route.abort", () -> {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("errorCode", errorCode);
|
||||
sendMessage("abort", params);
|
||||
sendMessageAsync("abort", params);
|
||||
});
|
||||
}
|
||||
|
||||
boolean isHandled() {
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(ResumeOptions options) {
|
||||
withLogging("Route.resume", () -> resumeImpl(options));
|
||||
startHandling();
|
||||
applyOverrides(convertType(options, FallbackOptions.class));
|
||||
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume()));
|
||||
}
|
||||
|
||||
private void resumeImpl(ResumeOptions options) {
|
||||
@Override
|
||||
public void fallback(FallbackOptions options) {
|
||||
if (handled) {
|
||||
throw new PlaywrightException("Route is already handled!");
|
||||
}
|
||||
applyOverrides(options);
|
||||
}
|
||||
|
||||
private void applyOverrides(FallbackOptions options) {
|
||||
if (options == null) {
|
||||
options = new ResumeOptions();
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
if (options.url != null) {
|
||||
params.addProperty("url", options.url);
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", Serialization.toProtocol(options.headers));
|
||||
return;
|
||||
}
|
||||
RequestImpl.FallbackOverrides overrides = new RequestImpl.FallbackOverrides();
|
||||
overrides.url = options.url;
|
||||
overrides.method = options.method;
|
||||
overrides.headers = options.headers;
|
||||
if (options.postData != null) {
|
||||
byte[] bytes = null;
|
||||
if (options.postData instanceof byte[]) {
|
||||
bytes = (byte[]) options.postData;
|
||||
} else if (options.postData instanceof String) {
|
||||
bytes = ((String) options.postData).getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
throw new PlaywrightException("postData must be either String or byte[], found: " + options.postData.getClass().getName());
|
||||
}
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
params.addProperty("postData", base64);
|
||||
overrides.postData = getPostDataBytes(options.postData);
|
||||
}
|
||||
sendMessage("continue", params);
|
||||
request().applyFallbackOverrides(overrides);
|
||||
}
|
||||
|
||||
private void resumeImpl(RequestImpl.FallbackOverrides options) {
|
||||
JsonObject params = new JsonObject();
|
||||
if (options != null) {
|
||||
if (options.url != null) {
|
||||
params.addProperty("url", options.url);
|
||||
}
|
||||
if (options.method != null) {
|
||||
params.addProperty("method", options.method);
|
||||
}
|
||||
if (options.headers != null) {
|
||||
params.add("headers", Serialization.toProtocol(options.headers));
|
||||
}
|
||||
if (options.postData != null) {
|
||||
String base64 = Base64.getEncoder().encodeToString(options.postData);
|
||||
params.addProperty("postData", base64);
|
||||
}
|
||||
}
|
||||
sendMessageAsync("continue", params);
|
||||
}
|
||||
|
||||
private static byte[] getPostDataBytes(Object postData) {
|
||||
if (postData instanceof byte[]) {
|
||||
return (byte[]) postData;
|
||||
}
|
||||
if (postData instanceof String) {
|
||||
return ((String) postData).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
throw new PlaywrightException("postData must be either String or byte[], found: " + postData.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fulfill(FulfillOptions options) {
|
||||
startHandling();
|
||||
withLogging("Route.fulfill", () -> fulfillImpl(options));
|
||||
}
|
||||
|
||||
@@ -86,16 +123,30 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
options = new FulfillOptions();
|
||||
}
|
||||
|
||||
int status = options.status == null ? 200 : options.status;
|
||||
String body = "";
|
||||
Integer status = options.status;
|
||||
Map<String, String> headersOption = options.headers;
|
||||
String fetchResponseUid = null;
|
||||
|
||||
if (options.response != null) {
|
||||
if (status == null) {
|
||||
status = options.response.status();
|
||||
}
|
||||
if (headersOption == null) {
|
||||
headersOption = options.response.headers();
|
||||
}
|
||||
}
|
||||
if (status == null) {
|
||||
status = 200;
|
||||
}
|
||||
String body = null;
|
||||
boolean isBase64 = false;
|
||||
int length = 0;
|
||||
if (options.path != null) {
|
||||
try {
|
||||
byte[] buffer = Files.readAllBytes(options.path);
|
||||
body = Base64.getEncoder().encodeToString(buffer);
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
byte[] buffer = Files.readAllBytes(options.path);
|
||||
body = Base64.getEncoder().encodeToString(buffer);
|
||||
isBase64 = true;
|
||||
length = buffer.length;
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to read from file: " + options.path, e);
|
||||
}
|
||||
@@ -107,11 +158,22 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
body = Base64.getEncoder().encodeToString(options.bodyBytes);
|
||||
isBase64 = true;
|
||||
length = options.bodyBytes.length;
|
||||
} else if (options.response != null) {
|
||||
APIResponseImpl response = (APIResponseImpl) options.response;
|
||||
if (response.context.connection == connection) {
|
||||
fetchResponseUid = response.fetchUid();
|
||||
} else {
|
||||
byte[] bodyBytes = response.body();
|
||||
body = Base64.getEncoder().encodeToString(bodyBytes);
|
||||
isBase64 = true;
|
||||
length = bodyBytes.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
if (options.headers != null) {
|
||||
for (Map.Entry<String, String> h : options.headers.entrySet()) {
|
||||
if (headersOption != null) {
|
||||
for (Map.Entry<String, String> h : headersOption.entrySet()) {
|
||||
headers.put(h.getKey().toLowerCase(), h.getValue());
|
||||
}
|
||||
}
|
||||
@@ -128,11 +190,29 @@ public class RouteImpl extends ChannelOwner implements Route {
|
||||
params.add("headers", Serialization.toProtocol(headers));
|
||||
params.addProperty("isBase64", isBase64);
|
||||
params.addProperty("body", body);
|
||||
sendMessage("fulfill", params);
|
||||
if (fetchResponseUid != null) {
|
||||
params.addProperty("fetchResponseUid", fetchResponseUid);
|
||||
}
|
||||
sendMessageAsync("fulfill", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request request() {
|
||||
return connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
|
||||
public RequestImpl request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
void redirectNavigationRequest(String redirectURL) {
|
||||
startHandling();
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("url", redirectURL);
|
||||
// TODO: _raceWithPageClose ?
|
||||
sendMessageAsync("redirectNavigationRequest", params);
|
||||
}
|
||||
|
||||
private void startHandling() {
|
||||
if (handled) {
|
||||
throw new PlaywrightException("Route is already handled!");
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
|
||||
import com.microsoft.playwright.Route;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -29,15 +30,29 @@ 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;
|
||||
}
|
||||
|
||||
void handle(RouteImpl route) {
|
||||
handler.accept(route);
|
||||
}
|
||||
|
||||
boolean decrementRemainingCallCount() {
|
||||
if (times == null) {
|
||||
return false;
|
||||
}
|
||||
--times;
|
||||
return 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) {
|
||||
@@ -50,13 +65,23 @@ class Router {
|
||||
return routes.size();
|
||||
}
|
||||
|
||||
boolean handle(Route route) {
|
||||
for (RouteInfo info : routes) {
|
||||
if (info.matcher.test(route.request().url())) {
|
||||
info.handler.accept(route);
|
||||
return true;
|
||||
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
|
||||
HandleResult handle(RouteImpl route) {
|
||||
HandleResult result = HandleResult.NoMatchingHandler;
|
||||
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
|
||||
RouteInfo info = it.next();
|
||||
if (!info.matcher.test(route.request().url())) {
|
||||
continue;
|
||||
}
|
||||
if (info.decrementRemainingCallCount()) {
|
||||
it.remove();
|
||||
}
|
||||
result = HandleResult.FoundMatchingHandler;
|
||||
info.handle(route);
|
||||
if (route.isHandled()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user