Compare commits

...

87 Commits

Author SHA1 Message Date
Simon Knott
eb1fea9907
fix(trace): waitForLoadState title (#1840) 2025-09-08 09:49:00 +02:00
dependabot[bot]
dd99ce8b34
chore(deps): bump the actions group with 2 updates (#1838) 2025-09-04 16:18:58 -07:00
dependabot[bot]
ed8e9c434f
chore(deps): bump the all group across 1 directory with 7 updates (#1839) 2025-09-04 16:18:15 -07:00
Yury Semikhatsky
aee298b293
chore: rename headful -> headed (#1835) 2025-08-27 09:51:30 -07:00
Max Schmitt
fd2ab4708a
devops: enable retries in Docker tests (#1834) 2025-08-26 21:50:28 +02:00
Max Schmitt
2a6cdff664
chore: migrate Trace Viewer tests to use real Trace viewer (#1830) 2025-08-26 10:43:18 +02:00
Max Schmitt
44161e0558
chore: fix Maven test commands (#1832) 2025-08-26 00:35:06 +02:00
Max Schmitt
954b1c43ef
refactor: remove unused ImplUtils class (#1833) 2025-08-25 15:29:53 -07:00
Simon Knott
f4c7b9734f
chore: roll 1.55.0 (#1827) 2025-08-21 17:09:15 +02:00
Yury Semikhatsky
dd87b300fb
fix: npe in page.pause() (#1828) 2025-08-18 15:51:17 -07:00
Janne Hyötylä
f83c03af68
fix: Fix masking in single element screenshots. (#1825) 2025-08-11 11:40:00 -07:00
JONGSHIN
d26dd0b112
fix: Replaced classLoader in DriverJar (#1811) 2025-07-31 11:03:19 -07:00
Yury Semikhatsky
0cf8c4e17f
chore: unflake tracing test (#1822) 2025-07-21 11:32:08 -07:00
Yury Semikhatsky
1fb593e1e2
chore: roll 1.54.1 (#1821) 2025-07-21 11:06:39 -07:00
Simon Knott
915ee8d64c
chore: roll 1.54.0-alpha-2025-07-09 (#1817) 2025-07-09 10:29:18 +02:00
Yury Semikhatsky
9df2165e93
devops: fix release branch name check (#1813) 2025-06-24 13:20:20 -07:00
Yury Semikhatsky
859eb9b8b8
chore: require explicit timeout parameter in sendMessage() (#1807) 2025-06-23 10:37:40 -07:00
Yury Semikhatsky
f28cb55795
chore: roll 1.53.1 (#1808) 2025-06-23 09:45:36 -07:00
Max Schmitt
07867c2db5
devops: ignore tests in CodeQL checks (#1805) 2025-06-20 16:25:15 -07:00
Simon Knott
b4151b1231
chore: remove withLogging (#1804) 2025-06-13 15:33:22 -07:00
Simon Knott
4698b91d8e
chore: roll 1.53.0 (#1801) 2025-06-12 12:35:29 +02:00
Simon Knott
8a89e36ce3
chore: roll to 1.53.0-alpha-2025-05-21 (#1798) 2025-06-10 18:08:32 +02:00
campersau
f1e6100b33
fix(docker): set default shell encoding (#1793) 2025-05-30 16:13:42 +01:00
Yury Semikhatsky
fddb146d73
devops: trigger publish on new tag (#1787) 2025-05-02 10:55:07 -07:00
Max Schmitt
9ad596ac75
devops: add linux-arm64 Docker tests (#1784) 2025-05-01 19:21:22 +02:00
Simon Knott
8cca01851a
chore: roll 1.52.0 driver, implement new features (#1780) 2025-04-29 09:13:50 -07:00
dependabot[bot]
478417bb56
chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.2 to 3.5.3 in the all group (#1774) 2025-04-01 11:38:02 -07:00
Max Schmitt
739202fddf
test: do not send Content-Length header for HEAD requests (#1771) 2025-03-20 18:41:01 +01:00
Max Schmitt
8593941005
test: fix 'SLF4J(W): No SLF4J providers were found.' warning (#1772) 2025-03-20 18:38:34 +01:00
Max Schmitt
b2852f5d57
devops: migrate to GitHub App for automation (#1770) 2025-03-19 14:24:34 +01:00
Yury Semikhatsky
6c059e351f
chore: roll driver to 1.51.1 (#1768) 2025-03-17 12:03:43 -07:00
Yury Semikhatsky
e86911ed2a
docs: remove obsolete toc from readme (#1767) 2025-03-17 19:37:04 +01:00
Yury Semikhatsky
17cc3b8297
docs: abridge readme, redirect to playwright.dev (#1766) 2025-03-17 11:17:28 -07:00
dependabot[bot]
43016df241
chore(deps): bump the all group with 3 updates (#1764) 2025-03-17 11:16:44 -07:00
dependabot[bot]
ba4eb3ce7d
chore(deps): bump the actions group with 2 updates (#1765) 2025-03-17 11:16:22 -07:00
Max Schmitt
ba7dd3cd89
devops: update GitHub Action workflows via dependabot (#1763) 2025-03-17 19:05:26 +01:00
Max Schmitt
5e37b7f5ca
devops: add DevContainer config (#1762) 2025-03-17 19:02:33 +01:00
Yury Semikhatsky
84eaf8f3cb
devops: retry failures up to 3 times on GHA (#1758) 2025-03-10 11:50:10 -07:00
Yury Semikhatsky
c090824c93
chore: roll 1.51.0 driver, implement new features (#1757) 2025-03-07 19:02:19 -08:00
dependabot[bot]
add7f56117
chore(deps): bump the all group with 7 updates (#1754) 2025-03-04 11:25:31 -08:00
Max Schmitt
676f38d22c
devops: use PME environment for ESRP publishing (#1755) 2025-03-03 16:52:51 +01:00
Max Schmitt
bc82f2fa68
chore: auto update example pw version in pom (#1748) 2025-02-13 10:08:42 +01:00
stevenfuhr
995cf902fb
fix: add pfxBase64 to jsonCert instead of params (#1746) 2025-02-07 12:33:20 -08:00
Yury Semikhatsky
87152ecc71
devops: make publish step type: releaseJob (#1744) 2025-02-06 11:28:50 -08:00
Yury Semikhatsky
dbc0478e40
chore: roll 1.50.1-beta (#1739) 2025-02-03 09:22:37 -08:00
dependabot[bot]
9eb1db9034
chore(deps): bump com.google.code.gson:gson from 2.11.0 to 2.12.1 in the all group (#1738) 2025-02-03 08:27:25 -08:00
Max Schmitt
4820088457
fix(urlMatcher): normalize URLs to align with Node.js parser behavior (#1734) 2025-01-27 10:26:39 -08:00
Max Schmitt
fcd0444c57
fix(webSocketRoute): resolve URL against baseURL (#1722) 2025-01-27 15:43:47 +01:00
Yury Semikhatsky
067e69f339
chore: roll 1.50.0 (#1733) 2025-01-23 12:14:30 -08:00
Yury Semikhatsky
015939b150
feat: roll driver to 1.50 alpha (#1729) 2025-01-17 09:21:19 -08:00
Adam Gastineau
eb8cf62d74
fix(tracing): Properly log Clock calls (#1727) 2025-01-16 07:04:53 -08:00
dependabot[bot]
308b9913e7
chore(deps): bump the all group with 5 updates (#1723) 2025-01-02 10:36:48 -08:00
Max Schmitt
6b621ce6f7
chore: refactor UrlMatcher (#1720) 2024-12-28 10:47:01 +01:00
Yury Semikhatsky
42d0203b49
fix: waitForCondition should not call predicate after it returned true (#1721) 2024-12-19 12:25:42 -08:00
Max Schmitt
c591a1470a
test: do not create stray files when running tests (#1718) 2024-12-18 11:49:18 +01:00
dependabot[bot]
eb08046e94
chore(deps): bump the all group with 2 updates (#1710) 2024-12-02 10:49:48 -08:00
Yury Semikhatsky
2ff37da5f5
chore: mark 1.50 snapshot (#1702) 2024-11-18 17:01:09 -08:00
Yury Semikhatsky
ee99afc3a3
chore: print actual message for TestBrowserContextCDPSession.shouldDe… (#1701) 2024-11-18 16:25:10 -08:00
Yury Semikhatsky
34017a26a3
chore: roll 1.49.0 (#1700) 2024-11-18 15:25:34 -08:00
Yury Semikhatsky
29f58a5840
test: unflake TestPageClock (#1699) 2024-11-15 14:38:02 -08:00
Yury Semikhatsky
d2d78a7299
chore: stop using microsoft/playwright-github-action@v1 (#1698) 2024-11-15 11:20:37 -08:00
Yury Semikhatsky
6e66ee7c35
chore: roll 1.49-beta (#1697) 2024-11-15 09:24:21 -08:00
dependabot[bot]
4bda800e11
chore(deps): bump the all group with 4 updates (#1695) 2024-11-14 08:54:00 -08:00
Max Schmitt
2cce9776be
devops: stop publishing Ubuntu 20.04 (#1690) 2024-10-21 17:00:06 +02:00
Yury Semikhatsky
20b13ad0c0
chore: roll driver 1.48.1 (#1687) 2024-10-17 12:06:13 -07:00
dependabot[bot]
08ac52ca53
chore(deps): bump the all group with 4 updates (#1680) 2024-10-17 11:56:19 -07:00
Yury Semikhatsky
9c220cd359
test: update web socket tests to properly dispatch ws messages (#1683) 2024-10-03 11:19:15 -07:00
Yury Semikhatsky
ab443d1638
chore: roll 1.48 beta driver (#1681) 2024-10-02 14:21:13 -07:00
Max Schmitt
186aede95c
devops: use wget for driver downloads (#1679) 2024-10-01 09:39:38 +02:00
Yury Semikhatsky
db52fa94e7
docs: remove snapshot badge from README 2024-09-12 17:04:25 -07:00
Yury Semikhatsky
ee18e1a499
chore: roll 1.47.0-beta-1726138322000 (#1672) 2024-09-12 15:02:49 -07:00
Yury Semikhatsky
9cf4bf2263
docs: Update ROLLING.md with new roll script 2024-09-12 12:03:29 -07:00
Yury Semikhatsky
36350f3c5c
chore: roll 1.47.0 (#1670) 2024-09-09 11:06:17 -07:00
Max Schmitt
f3476c68ff
test: fix client-certificate tests (#1669) 2024-09-09 09:05:37 -07:00
Max Schmitt
8fd8f1c831
test: remove per-context proxy hacks for Windows/Chromium (#1668) 2024-09-09 10:09:00 +02:00
dependabot[bot]
7d2066693b
chore(deps): bump the all group with 2 updates (#1664) 2024-09-03 08:55:04 -07:00
Yury Semikhatsky
6b01b878cb
chore: roll driver to 1.47.0 alpha 2024 08 28 (#1663) 2024-08-28 16:02:34 -07:00
uchagani
2e32eb704f
feat(junit): Implement automatic saving of traces and screenshots via fixtures (#1560) 2024-08-28 13:52:07 -07:00
Chris Kocel
b81b144680
fix: null check in ListenerCollection notify method (#1661) 2024-08-28 13:50:55 -07:00
Yury Semikhatsky
256e41a505
docs: add SUPPORT.md (#1662) 2024-08-28 11:09:38 -07:00
Yury Semikhatsky
3054364101
devops(dependabot): update all deps in single PR monthly (#1656) 2024-08-19 13:21:31 -07:00
dependabot[bot]
40b8802874
chore(deps): bump org.apache.maven.plugins:maven-deploy-plugin from 3.1.2 to 3.1.3 (#1654) 2024-08-19 12:11:29 -07:00
dependabot[bot]
49f53fceaf
chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.3.1 to 3.4.0 (#1653) 2024-08-19 12:11:13 -07:00
dependabot[bot]
291c12a54c
chore(deps): bump org.apache.maven.plugins:maven-install-plugin from 3.1.2 to 3.1.3 (#1655) 2024-08-19 12:11:01 -07:00
dependabot[bot]
2b3413fad4
chore(deps): bump junit.version from 5.10.3 to 5.11.0 (#1652) 2024-08-19 11:16:59 -07:00
dependabot[bot]
3eab530e95
chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.4 to 3.2.5 (#1650) 2024-08-19 11:16:40 -07:00
Max Schmitt
7d35be4f89
devops: publish Ubuntu 24.04 Docker image (#1649) 2024-08-12 19:02:17 +02:00
195 changed files with 7232 additions and 3303 deletions

View File

@ -1,6 +1,10 @@
trigger: none
pr: none pr: none
trigger:
tags:
include:
- '*'
resources: resources:
repositories: repositories:
- repository: 1esPipelines - repository: 1esPipelines
@ -25,11 +29,16 @@ extends:
stages: stages:
- stage: Stage - stage: Stage
jobs: jobs:
- job: HostJob - job: Build
templateContext:
outputs:
- output: pipelineArtifact
path: $(Build.ArtifactStagingDirectory)/esrp-build
artifact: esrp-build
steps: steps:
- bash: | - bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then if [[ ! "$CURRENT_BRANCH" =~ ^v1\..* ]]; then
echo "Can only publish from a release branch." echo "Can only publish from a release tag branch (v1.*)."
echo "Unexpected branch name: $CURRENT_BRANCH" echo "Unexpected branch name: $CURRENT_BRANCH"
exit 1 exit 1
fi fi
@ -50,16 +59,27 @@ extends:
- bash: ./scripts/download_driver.sh - bash: ./scripts/download_driver.sh
displayName: 'Download driver' displayName: 'Download driver'
- bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(pwd)/local-build - bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(Build.ArtifactStagingDirectory)/esrp-build
displayName: 'Build and deploy to a local directory' displayName: 'Build and deploy to a local directory'
env: env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- task: EsrpRelease@7 - job: Publish
dependsOn: Build
templateContext:
type: releaseJob
isProduction: true
inputs: inputs:
connectedservicename: 'Playwright-ESRP-Azure' - input: pipelineArtifact
keyvaultname: 'pw-publishing-secrets' artifactName: esrp-build
authcertname: 'ESRP-Release-Auth' targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
steps:
- checkout: none
- task: EsrpRelease@9
inputs:
connectedservicename: 'Playwright-ESRP-PME'
usemanagedidentity: true
keyvaultname: 'playwright-esrp-pme'
signcertname: 'ESRP-Release-Sign' signcertname: 'ESRP-Release-Sign'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5' clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution' intent: 'PackageDistribution'
@ -67,11 +87,11 @@ extends:
# Keeping it commented out as a workaround for: # Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary # https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder' # contentsource: 'folder'
folderlocation: './local-build' folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
waitforreleasecompletion: true waitforreleasecompletion: true
owners: 'yurys@microsoft.com' owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com' approvers: 'maxschmitt@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com' serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright' mainpublisher: 'Playwright'
domaintenantid: '72f988bf-86f1-41af-91ab-2d7cd011db47' domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
displayName: 'ESRP Release to Maven' displayName: 'ESRP Release to Maven'

View File

@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:1-21-bookworm",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installGradle": "false",
"installMaven": "true"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@ -3,8 +3,25 @@ updates:
- package-ecosystem: "maven" - package-ecosystem: "maven"
directory: "/" # Location of the pom.xml file directory: "/" # Location of the pom.xml file
schedule: schedule:
interval: "weekly" interval: "monthly"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
groups:
# Create a group of dependencies to be updated together in one pull request
all:
applies-to: version-updates
patterns:
- "*"
update-types:
- "minor"
- "patch"
allow: allow:
- dependency-type: "direct" # Optional: Only update direct dependencies - dependency-type: "direct" # Optional: Only update direct dependencies
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies - dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
actions:
patterns:
- "*"

View File

@ -3,11 +3,6 @@ on:
release: release:
types: [published] types: [published]
workflow_dispatch: workflow_dispatch:
inputs:
is_release:
required: true
type: boolean
description: "Is this a release image?"
jobs: jobs:
publish-canary-docker: publish-canary-docker:
name: publish to DockerHub name: publish to DockerHub
@ -18,7 +13,7 @@ jobs:
environment: Docker environment: Docker
if: github.repository == 'microsoft/playwright-java' if: github.repository == 'microsoft/playwright-java'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Azure login - name: Azure login
uses: azure/login@v2 uses: azure/login@v2
with: with:
@ -31,8 +26,5 @@ jobs:
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with: with:
platforms: arm64 platforms: arm64
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- run: ./utils/docker/publish_docker.sh stable - 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')

View File

@ -8,6 +8,8 @@ on:
branches: branches:
- main - main
- release-* - release-*
env:
PW_MAX_RETRIES: 3
jobs: jobs:
dev: dev:
timeout-minutes: 30 timeout-minutes: 30
@ -18,10 +20,9 @@ jobs:
browser: [chromium, firefox, webkit] browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v2 uses: actions/setup-java@v5
with: with:
distribution: zulu distribution: zulu
java-version: 8 java-version: 8
@ -30,6 +31,8 @@ jobs:
run: scripts/download_driver.sh run: scripts/download_driver.sh
- name: Build & Install - name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests - name: Run tests
run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env: env:
@ -62,14 +65,13 @@ jobs:
browser-channel: msedge browser-channel: msedge
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
- uses: microsoft/playwright-github-action@v1
- name: Install Media Pack - name: Install Media Pack
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
shell: powershell shell: powershell
run: Install-WindowsFeature Server-Media-Foundation run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v2 uses: actions/setup-java@v5
with: with:
distribution: zulu distribution: zulu
java-version: 8 java-version: 8
@ -78,6 +80,8 @@ jobs:
run: scripts/download_driver.sh run: scripts/download_driver.sh
- name: Build & Install - name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Install MS Edge - name: Install MS Edge
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest' if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
shell: bash shell: bash
@ -96,10 +100,9 @@ jobs:
browser: [chromium, firefox, webkit] browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 21 - name: Set up JDK 21
uses: actions/setup-java@v2 uses: actions/setup-java@v5
with: with:
distribution: adopt distribution: adopt
java-version: 21 java-version: 21
@ -108,6 +111,8 @@ jobs:
run: scripts/download_driver.sh run: scripts/download_driver.sh
- name: Build & Install - name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests - name: Run tests
run: mvn test --no-transfer-progress --fail-at-end run: mvn test --no-transfer-progress --fail-at-end
env: env:

View File

@ -13,9 +13,9 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@ -20,16 +20,33 @@ jobs:
test: test:
name: Test name: Test
timeout-minutes: 120 timeout-minutes: 120
runs-on: ubuntu-22.04 runs-on: ${{ matrix.runs-on }}
env:
PW_MAX_RETRIES: 3
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
flavor: [focal, jammy] flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v5
- name: Build Docker image - name: Build Docker image
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Test
run: | run: |
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)" ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Start container
run: |
CONTAINER_ID=$(docker run --rm -e CI -e PW_MAX_RETRIES --ipc=host -v "$(pwd)":/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
- name: Run test in container
run: |
docker exec "$CONTAINER_ID" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
- name: Test ClassLoader
run: |
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
- name: Stop container
run: |
docker stop "$CONTAINER_ID"

View File

@ -1,21 +0,0 @@
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-browsers/dispatches
env:
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}

View File

@ -19,12 +19,15 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v5
- uses: microsoft/playwright-github-action@v1
- name: Download drivers - name: Download drivers
run: scripts/download_driver.sh run: scripts/download_driver.sh
- name: Regenerate APIs - name: Regenerate APIs
run: scripts/generate_api.sh run: scripts/generate_api.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Update browser versions in README - name: Update browser versions in README
run: scripts/update_readme.sh run: scripts/update_readme.sh
- name: Verify API is up to date - name: Verify API is up to date

View File

@ -32,9 +32,9 @@ scripts/download_driver.sh
mvn compile mvn compile
mvn test mvn test
# Executing a single test # Executing a single test
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class # Executing a single test class
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
``` ```
### Generating API ### Generating API

3
CodeQL.yml Normal file
View File

@ -0,0 +1,3 @@
path_classifiers:
tests:
- "playwright/src/test/**"

158
README.md
View File

@ -2,7 +2,6 @@
[![javadoc](https://javadoc.io/badge2/com.microsoft.playwright/playwright/javadoc.svg)](https://javadoc.io/doc/com.microsoft.playwright/playwright) [![javadoc](https://javadoc.io/badge2/com.microsoft.playwright/playwright/javadoc.svg)](https://javadoc.io/doc/com.microsoft.playwright/playwright)
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright) [![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright)
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.microsoft.playwright/playwright.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
[![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
#### [Website](https://playwright.dev/java/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html) #### [Website](https://playwright.dev/java/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
@ -11,59 +10,19 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows | | | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: | | :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->128.0.6613.18<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | ✅ | ✅ | ✅ | | WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->128.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox <!-- GEN:firefox-version -->141.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/intro#system-requirements) for details. ## Documentation
* [Usage](#usage) [https://playwright.dev/java/docs/intro](https://playwright.dev/java/docs/intro)
- [Add Maven dependency](#add-maven-dependency)
- [Is Playwright thread-safe?](#is-playwright-thread-safe)
* [Examples](#examples)
- [Page screenshot](#page-screenshot)
- [Mobile and geolocation](#mobile-and-geolocation)
- [Evaluate JavaScript in browser](#evaluate-javascript-in-browser)
- [Intercept network requests](#intercept-network-requests)
* [Documentation](#documentation)
* [Contributing](#contributing)
* [Is Playwright for Java ready?](#is-playwright-for-java-ready)
## Usage ## API Reference
Playwright requires **Java 8** or newer. [https://playwright.dev/java/docs/api/class-playwright](https://playwright.dev/java/docs/api/class-playwright)
#### Add Maven dependency ## Example
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add one dependency to your Maven `pom.xml` file as described below. If you're not familiar with Maven please refer to its [documentation](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
To run Playwright simply add following dependency to your Maven project:
```xml
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.41.0</version>
</dependency>
```
To run Playwright using Gradle add following dependency to your build.gradle file:
```gradle
dependencies {
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.41.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.
## Examples
You can find Maven project with the examples [here](./examples).
#### Page screenshot
This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots. This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots.
@ -95,100 +54,9 @@ public class PageScreenshot {
} }
``` ```
#### Mobile and geolocation ## Other languages
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot. More comfortable in another programming language? [Playwright](https://playwright.dev) is also available in
- [Node.js (JavaScript / TypeScript)](https://playwright.dev/docs/intro),
```java - [Python](https://playwright.dev/python/docs/intro).
import com.microsoft.playwright.options.*; - [.NET](https://playwright.dev/dotnet/docs/intro),
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-bs-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
}
```
#### Evaluate JavaScript in browser
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
```java
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
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://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
}
}
}
```
#### Intercept network requests
This code snippet sets up request routing for a WebKit page to log all network requests.
```java
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
}
}
```
## Documentation
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/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
## Is Playwright for Java ready?
Yes, Playwright for Java is ready. v1.10.0 is the first stable release. Going forward we will adhere to [semantic versioning](https://semver.org/) of the API.

View File

@ -2,11 +2,10 @@
* make sure to have at least Java 8 and Maven 3.6.3 * make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java * clone playwright for java: http://github.com/microsoft/playwright-java
* set new driver version in `scripts/DRIVER_VERSION` * `./scripts/roll_driver.sh 1.47.0-beta-1726138322000`
* regenerate API: `./scripts/download_driver.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll * commit & send PR with the roll
### Finding driver version ## Finding driver version
For development versions of Playwright, you can find the latest version by looking at [publish_canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) workflow -> `publish canary NPM & Publish canary Docker` -> `build & publish driver` step -> `PACKAGE_VERSION` For development versions of Playwright, you can find the latest version by looking at [publish_canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) workflow -> `publish canary NPM & Publish canary Docker` -> `build & publish driver` step -> `PACKAGE_VERSION`
<img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75"> <img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75">

17
SUPPORT.md Normal file
View File

@ -0,0 +1,17 @@
# Support
## How to file issues and get help
This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues][gh-issues] before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template.
For help and questions about using this project, please see the [docs site for Playwright for Java][docs].
Join our community [Discord Server][discord-server] to connect with other developers using Playwright and ask questions in our 'help-playwright' forum.
## Microsoft Support Policy
Support for Playwright for Java is limited to the resources listed above.
[gh-issues]: https://github.com/microsoft/playwright-java/issues/
[docs]: https://playwright.dev/java/
[discord-server]: https://aka.ms/playwright/discord

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId> <artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version> <version>1.50.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>driver-bundle</artifactId> <artifactId>driver-bundle</artifactId>

View File

@ -114,7 +114,7 @@ public class DriverJar extends Driver {
} }
public static URI getDriverResourceURI() throws URISyntaxException { public static URI getDriverResourceURI() throws URISyntaxException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader(); ClassLoader classloader = DriverJar.class.getClassLoader();
return classloader.getResource("driver/" + platformDir()).toURI(); return classloader.getResource("driver/" + platformDir()).toURI();
} }

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId> <artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version> <version>1.50.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>driver</artifactId> <artifactId>driver</artifactId>

View File

@ -6,16 +6,17 @@
<groupId>org.example</groupId> <groupId>org.example</groupId>
<artifactId>examples</artifactId> <artifactId>examples</artifactId>
<version>1.46.0-SNAPSHOT</version> <version>1.50.0-SNAPSHOT</version>
<name>Playwright Client Examples</name> <name>Playwright Client Examples</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.55.0</playwright.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
<version>1.41.0</version> <version>${playwright.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -7,7 +7,7 @@
<parent> <parent>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId> <artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version> <version>1.50.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
@ -57,10 +57,23 @@
<groupId>org.java-websocket</groupId> <groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId> <artifactId>Java-WebSocket</artifactId>
</dependency> </dependency>
<!--
The following slf4j-simple dependency resolves the warning:
'SLF4J(W): No SLF4J providers were found.'
This warning is produced by the org.java-websocket library.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.opentest4j</groupId> <groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId> <artifactId>opentest4j</artifactId>

View File

@ -46,12 +46,14 @@ public interface APIRequest {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
@ -61,6 +63,10 @@ public interface APIRequest {
* An object containing additional HTTP headers to be sent with every request. Defaults to none. * An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/ */
public Map<String, String> extraHTTPHeaders; public Map<String, String> extraHTTPHeaders;
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public Boolean failOnStatusCode;
/** /**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If * Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses. * no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@ -70,6 +76,12 @@ public interface APIRequest {
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}. * Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/ */
public Boolean ignoreHTTPSErrors; public Boolean ignoreHTTPSErrors;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public Integer maxRedirects;
/** /**
* Network proxy settings. * Network proxy settings.
*/ */
@ -121,12 +133,14 @@ public interface APIRequest {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
@ -142,6 +156,13 @@ public interface APIRequest {
this.extraHTTPHeaders = extraHTTPHeaders; this.extraHTTPHeaders = extraHTTPHeaders;
return this; return this;
} }
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public NewContextOptions setFailOnStatusCode(boolean failOnStatusCode) {
this.failOnStatusCode = failOnStatusCode;
return this;
}
/** /**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If * Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses. * no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@ -164,6 +185,15 @@ public interface APIRequest {
this.ignoreHTTPSErrors = ignoreHTTPSErrors; this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this; return this;
} }
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public NewContextOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
/** /**
* Network proxy settings. * Network proxy settings.
*/ */

View File

@ -58,12 +58,23 @@ public interface APIRequestContext {
} }
} }
class StorageStateOptions { class StorageStateOptions {
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public Boolean indexedDB;
/** /**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * 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. * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/ */
public Path path; public Path path;
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
return this;
}
/** /**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * 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. * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
@ -129,8 +140,7 @@ public interface APIRequestContext {
* }</pre> * }</pre>
* *
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code * <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code * multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* multipart} parameter:
* <pre>{@code * <pre>{@code
* // Pass file path to the form data constructor: * // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv"); * Path file = Paths.get("team.csv");
@ -167,8 +177,7 @@ public interface APIRequestContext {
* }</pre> * }</pre>
* *
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code * <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code * multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* multipart} parameter:
* <pre>{@code * <pre>{@code
* // Pass file path to the form data constructor: * // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv"); * Path file = Paths.get("team.csv");
@ -204,8 +213,7 @@ public interface APIRequestContext {
* }</pre> * }</pre>
* *
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code * <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code * multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* multipart} parameter:
* <pre>{@code * <pre>{@code
* // Pass file path to the form data constructor: * // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv"); * Path file = Paths.get("team.csv");
@ -242,8 +250,7 @@ public interface APIRequestContext {
* }</pre> * }</pre>
* *
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code * <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code * multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* multipart} parameter:
* <pre>{@code * <pre>{@code
* // Pass file path to the form data constructor: * // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv"); * Path file = Paths.get("team.csv");

View File

@ -43,8 +43,8 @@ public interface APIResponse {
*/ */
Map<String, String> headers(); Map<String, String> headers();
/** /**
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with * An array with all the response HTTP headers associated with this response. Header names are not lower-cased. Headers
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times. * with multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
* *
* @since v1.16 * @since v1.16
*/ */

View File

@ -31,10 +31,10 @@ import java.util.regex.Pattern;
* public class Example { * public class Example {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox() * BrowserType firefox = playwright.firefox();
* Browser browser = firefox.launch(); * Browser browser = firefox.launch();
* Page page = browser.newPage(); * Page page = browser.newPage();
* page.navigate('https://example.com'); * page.navigate("https://example.com");
* browser.close(); * browser.close();
* } * }
* } * }
@ -101,23 +101,33 @@ public interface Browser extends AutoCloseable {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
*/ */
public List<ClientCertificate> clientCertificates; public List<ClientCertificate> clientCertificates;
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public Optional<ColorScheme> colorScheme; public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -179,10 +189,6 @@ public interface Browser extends AutoCloseable {
public List<String> permissions; public List<String> permissions;
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/ */
public Proxy proxy; public Proxy proxy;
/** /**
@ -316,12 +322,14 @@ public interface Browser extends AutoCloseable {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
@ -331,14 +339,25 @@ public interface Browser extends AutoCloseable {
return this; return this;
} }
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public NewContextOptions setColorScheme(ColorScheme colorScheme) { public NewContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme); this.colorScheme = Optional.ofNullable(colorScheme);
return this; return this;
} }
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public NewContextOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -446,20 +465,12 @@ public interface Browser extends AutoCloseable {
} }
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <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) { public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server)); return setProxy(new Proxy(server));
} }
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <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) { public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy; this.proxy = proxy;
@ -666,23 +677,33 @@ public interface Browser extends AutoCloseable {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
*/ */
public List<ClientCertificate> clientCertificates; public List<ClientCertificate> clientCertificates;
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public Optional<ColorScheme> colorScheme; public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -744,10 +765,6 @@ public interface Browser extends AutoCloseable {
public List<String> permissions; public List<String> permissions;
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/ */
public Proxy proxy; public Proxy proxy;
/** /**
@ -881,12 +898,14 @@ public interface Browser extends AutoCloseable {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
@ -896,14 +915,25 @@ public interface Browser extends AutoCloseable {
return this; return this;
} }
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public NewPageOptions setColorScheme(ColorScheme colorScheme) { public NewPageOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme); this.colorScheme = Optional.ofNullable(colorScheme);
return this; return this;
} }
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public NewPageOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -1011,20 +1041,12 @@ public interface Browser extends AutoCloseable {
} }
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <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) { public NewPageOptions setProxy(String server) {
return setProxy(new Proxy(server)); return setProxy(new Proxy(server));
} }
/** /**
* Network proxy settings to use with this context. Defaults to none. * Network proxy settings to use with this context. Defaults to none.
*
* <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) { public NewPageOptions setProxy(Proxy proxy) {
this.proxy = proxy; this.proxy = proxy;
@ -1249,10 +1271,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 * <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server. * browser server.
* *
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link * <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly * {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** calling {@link * explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* com.microsoft.playwright.Browser#close Browser.close()}. * calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
* *
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore. * <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
* *
@ -1268,10 +1290,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 * <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server. * browser server.
* *
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link * <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly * {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** calling {@link * explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* com.microsoft.playwright.Browser#close Browser.close()}. * calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
* *
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore. * <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
* *
@ -1321,7 +1343,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* // Create a new page in a pristine context. * // Create a new page in a pristine context.
* Page page = context.newPage(); * Page page = context.newPage();
* page.navigate('https://example.com'); * page.navigate("https://example.com");
* *
* // Graceful close up everything * // Graceful close up everything
* context.close(); * context.close();
@ -1348,7 +1370,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* // Create a new page in a pristine context. * // Create a new page in a pristine context.
* Page page = context.newPage(); * Page page = context.newPage();
* page.navigate('https://example.com'); * page.navigate("https://example.com");
* *
* // Graceful close up everything * // Graceful close up everything
* context.close(); * context.close();
@ -1396,7 +1418,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code * <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions() * browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json"))); * .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com'); * page.navigate("https://www.google.com");
* browser.stopTracing(); * browser.stopTracing();
* }</pre> * }</pre>
* *
@ -1420,7 +1442,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code * <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions() * browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json"))); * .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com'); * page.navigate("https://www.google.com");
* browser.stopTracing(); * browser.stopTracing();
* }</pre> * }</pre>
* *
@ -1443,7 +1465,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code * <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions() * browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json"))); * .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com'); * page.navigate("https://www.google.com");
* browser.stopTracing(); * browser.stopTracing();
* }</pre> * }</pre>
* *

View File

@ -30,8 +30,9 @@ 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 * <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. * context.
* *
* <p> Playwright allows creating "incognito" browser contexts with {@link com.microsoft.playwright.Browser#newContext * <p> Playwright allows creating isolated non-persistent browser contexts with {@link
* Browser.newContext()} method. "Incognito" browser contexts don't write any browsing data to disk. * com.microsoft.playwright.Browser#newContext Browser.newContext()} method. Non-persistent browser contexts don't write
* any browsing data to disk.
* <pre>{@code * <pre>{@code
* // Create a new incognito browser context * // Create a new incognito browser context
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
@ -406,12 +407,27 @@ public interface BrowserContext extends AutoCloseable {
} }
} }
class StorageStateOptions { class StorageStateOptions {
/**
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*/
public Boolean indexedDB;
/** /**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * 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. * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/ */
public Path path; public Path path;
/**
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
return this;
}
/** /**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * 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. * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
@ -580,7 +596,8 @@ public interface BrowserContext extends AutoCloseable {
*/ */
List<Page> backgroundPages(); List<Page> backgroundPages();
/** /**
* Returns the browser instance of the context. If it was launched as a persistent context null gets returned. * Gets the browser instance that owns the context. Returns {@code null} if the context is created outside of normal
* browser, e.g. Android or Electron.
* *
* @since v1.8 * @since v1.8
*/ */
@ -700,7 +717,7 @@ public interface BrowserContext extends AutoCloseable {
* public class Example { * public class Example {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit() * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url()); * context.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -747,7 +764,7 @@ public interface BrowserContext extends AutoCloseable {
* public class Example { * public class Example {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit() * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url()); * context.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -796,8 +813,9 @@ public interface BrowserContext extends AutoCloseable {
* public class Example { * public class Example {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit() * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeFunction("sha256", args -> { * context.exposeFunction("sha256", args -> {
* String text = (String) args[0]; * String text = (String) args[0];
* MessageDigest crypto; * MessageDigest crypto;
@ -832,10 +850,14 @@ public interface BrowserContext extends AutoCloseable {
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if * Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified. * specified.
* *
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values: * @param permissions A list of permissions to grant.
*
* <p> <strong>NOTE:</strong> Supported permissions differ between browsers, and even between different versions of the same browser. Any permission
* may stop working after an update.
*
* <p> Here are some permissions that may be supported by some browsers:
* <ul> * <ul>
* <li> {@code "accelerometer"}</li> * <li> {@code "accelerometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "ambient-light-sensor"}</li> * <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li> * <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li> * <li> {@code "camera"}</li>
@ -850,6 +872,7 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li> * <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li> * <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li> * <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul> * </ul>
* @since v1.8 * @since v1.8
*/ */
@ -860,10 +883,14 @@ public interface BrowserContext extends AutoCloseable {
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if * Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified. * specified.
* *
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values: * @param permissions A list of permissions to grant.
*
* <p> <strong>NOTE:</strong> Supported permissions differ between browsers, and even between different versions of the same browser. Any permission
* may stop working after an update.
*
* <p> Here are some permissions that may be supported by some browsers:
* <ul> * <ul>
* <li> {@code "accelerometer"}</li> * <li> {@code "accelerometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "ambient-light-sensor"}</li> * <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li> * <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li> * <li> {@code "camera"}</li>
@ -878,6 +905,7 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li> * <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li> * <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li> * <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul> * </ul>
* @since v1.8 * @since v1.8
*/ */
@ -926,7 +954,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -967,8 +995,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -982,7 +1010,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -1023,8 +1051,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -1036,7 +1064,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -1077,8 +1105,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -1092,7 +1120,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -1133,8 +1161,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -1146,7 +1174,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -1187,8 +1215,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -1202,7 +1230,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} will not intercept requests intercepted by * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.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 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"}. * Service Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
@ -1243,8 +1271,8 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -1256,7 +1284,7 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <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 * 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"}. * using request interception by setting {@code 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 * @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. * path} is a relative path, then it is resolved relative to the current working directory.
@ -1271,13 +1299,94 @@ public interface BrowserContext extends AutoCloseable {
* *
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <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 * 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"}. * using request interception by setting {@code 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 * @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. * path} is a relative path, then it is resolved relative to the current working directory.
* @since v1.23 * @since v1.23
*/ */
void routeFromHAR(Path har, RouteFromHAROptions options); void routeFromHAR(Path har, RouteFromHAROptions options);
/**
* This method allows to modify websocket connections that are made by any page in the browser context.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before creating any pages.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple handler that blocks some websocket messages. See {@code WebSocketRoute} for more details
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* return;
* ws.send(message);
* });
* ws.connect();
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(String url, Consumer<WebSocketRoute> handler);
/**
* This method allows to modify websocket connections that are made by any page in the browser context.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before creating any pages.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple handler that blocks some websocket messages. See {@code WebSocketRoute} for more details
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* return;
* ws.send(message);
* });
* ws.connect();
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(Pattern url, Consumer<WebSocketRoute> handler);
/**
* This method allows to modify websocket connections that are made by any page in the browser context.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before creating any pages.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple handler that blocks some websocket messages. See {@code WebSocketRoute} for more details
* and examples.
* <pre>{@code
* context.routeWebSocket("/ws", ws -> {
* ws.routeSend(message -> {
* if ("to-be-blocked".equals(message))
* return;
* ws.send(message);
* });
* ws.connect();
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(Predicate<String> url, Consumer<WebSocketRoute> handler);
/** /**
* This setting will change the default maximum navigation time for the following methods and related shortcuts: * This setting will change the default maximum navigation time for the following methods and related shortcuts:
* <ul> * <ul>
@ -1305,7 +1414,7 @@ public interface BrowserContext extends AutoCloseable {
* com.microsoft.playwright.BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()} take * com.microsoft.playwright.BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()} take
* priority over {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}. * priority over {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* *
* @param timeout Maximum time in milliseconds * @param timeout Maximum time in milliseconds. Pass {@code 0} to disable timeout.
* @since v1.8 * @since v1.8
*/ */
void setDefaultTimeout(double timeout); void setDefaultTimeout(double timeout);
@ -1344,7 +1453,7 @@ public interface BrowserContext extends AutoCloseable {
*/ */
void setOffline(boolean offline); void setOffline(boolean offline);
/** /**
* Returns storage state for this browser context, contains current cookies and local storage snapshot. * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
* *
* @since v1.8 * @since v1.8
*/ */
@ -1352,7 +1461,7 @@ public interface BrowserContext extends AutoCloseable {
return storageState(null); return storageState(null);
} }
/** /**
* Returns storage state for this browser context, contains current cookies and local storage snapshot. * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
* *
* @since v1.8 * @since v1.8
*/ */

View File

@ -172,9 +172,14 @@ public interface BrowserType {
*/ */
public List<String> args; public List<String> args;
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public Object channel; public Object channel;
/** /**
@ -204,6 +209,9 @@ public interface BrowserType {
/** /**
* Firefox user preferences. Learn more about the Firefox user preferences at <a * 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>. * href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/ */
public Map<String, Object> firefoxUserPrefs; public Map<String, Object> firefoxUserPrefs;
/** /**
@ -221,8 +229,8 @@ public interface BrowserType {
/** /**
* Whether to run browser in headless mode. More details for <a * 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://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} * href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* unless the {@code devtools} option is {@code true}. * the {@code devtools} option is {@code true}.
*/ */
public Boolean headless; public Boolean headless;
/** /**
@ -265,18 +273,28 @@ public interface BrowserType {
} }
@Deprecated @Deprecated
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public LaunchOptions setChannel(BrowserChannel channel) { public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel; this.channel = channel;
return this; return this;
} }
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public LaunchOptions setChannel(String channel) { public LaunchOptions setChannel(String channel) {
this.channel = channel; this.channel = channel;
@ -324,6 +342,9 @@ public interface BrowserType {
/** /**
* Firefox user preferences. Learn more about the Firefox user preferences at <a * 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>. * href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/ */
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) { public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs; this.firefoxUserPrefs = firefoxUserPrefs;
@ -353,8 +374,8 @@ public interface BrowserType {
/** /**
* Whether to run browser in headless mode. More details for <a * 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://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} * href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* unless the {@code devtools} option is {@code true}. * the {@code devtools} option is {@code true}.
*/ */
public LaunchOptions setHeadless(boolean headless) { public LaunchOptions setHeadless(boolean headless) {
this.headless = headless; this.headless = headless;
@ -446,9 +467,14 @@ public interface BrowserType {
*/ */
public Boolean bypassCSP; public Boolean bypassCSP;
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public Object channel; public Object channel;
/** /**
@ -460,23 +486,33 @@ public interface BrowserType {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
*/ */
public List<ClientCertificate> clientCertificates; public List<ClientCertificate> clientCertificates;
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public Optional<ColorScheme> colorScheme; public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -509,6 +545,9 @@ public interface BrowserType {
/** /**
* Firefox user preferences. Learn more about the Firefox user preferences at <a * 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>. * href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/ */
public Map<String, Object> firefoxUserPrefs; public Map<String, Object> firefoxUserPrefs;
/** /**
@ -538,8 +577,8 @@ public interface BrowserType {
/** /**
* Whether to run browser in headless mode. More details for <a * 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://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} * href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* unless the {@code devtools} option is {@code true}. * the {@code devtools} option is {@code true}.
*/ */
public Boolean headless; public Boolean headless;
/** /**
@ -734,18 +773,28 @@ public interface BrowserType {
} }
@Deprecated @Deprecated
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) { public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel; this.channel = channel;
return this; return this;
} }
/** /**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", * Browser distribution channel.
* "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>. * <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/ */
public LaunchPersistentContextOptions setChannel(String channel) { public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel; this.channel = channel;
@ -763,12 +812,14 @@ public interface BrowserType {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> An array of client certificates to be used. Each certificate object must have both {@code certPath} and {@code keyPath} * <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* or a single {@code pfxPath} to load the client certificate. Optionally, {@code passphrase} property should be provided * keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* if the certficiate is encrypted. The {@code origin} property should be provided with an exact match to the request * {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin that the certificate is valid for. * origin} property should be provided with an exact match to the request origin that the certificate is valid for.
* *
* <p> <strong>NOTE:</strong> Using Client Certificates in combination with Proxy Servers is not supported. * <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
* *
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by * <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}. * replacing {@code localhost} with {@code local.playwright}.
@ -778,14 +829,25 @@ public interface BrowserType {
return this; return this;
} }
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}. * feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/ */
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) { public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme); this.colorScheme = Optional.ofNullable(colorScheme);
return this; return this;
} }
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public LaunchPersistentContextOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>. * href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@ -836,6 +898,9 @@ public interface BrowserType {
/** /**
* Firefox user preferences. Learn more about the Firefox user preferences at <a * 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>. * href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/ */
public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) { public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs; this.firefoxUserPrefs = firefoxUserPrefs;
@ -889,8 +954,8 @@ public interface BrowserType {
/** /**
* Whether to run browser in headless mode. More details for <a * 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://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} * href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* unless the {@code devtools} option is {@code true}. * the {@code devtools} option is {@code true}.
*/ */
public LaunchPersistentContextOptions setHeadless(boolean headless) { public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless; this.headless = headless;
@ -1167,22 +1232,24 @@ public interface BrowserType {
} }
} }
/** /**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code * This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
* 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. * <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
* launches the browser (1.2.3 is compatible with 1.2.x).
*
* @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
* @since v1.8 * @since v1.8
*/ */
default Browser connect(String wsEndpoint) { default Browser connect(String wsEndpoint) {
return connect(wsEndpoint, null); return connect(wsEndpoint, null);
} }
/** /**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code * This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
* 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. * <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
* launches the browser (1.2.3 is compatible with 1.2.x).
*
* @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
* @since v1.8 * @since v1.8
*/ */
Browser connect(String wsEndpoint, ConnectOptions options); Browser connect(String wsEndpoint, ConnectOptions options);
@ -1193,6 +1260,11 @@ public interface BrowserType {
* *
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. * <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
* *
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222"); * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@ -1214,6 +1286,11 @@ public interface BrowserType {
* *
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. * <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
* *
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222"); * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@ -1308,11 +1385,20 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this * <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser. * context will automatically close the browser.
* *
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a * @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <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 * href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass * the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
* an empty string to use a temporary directory instead. *
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8 * @since v1.8
*/ */
default BrowserContext launchPersistentContext(Path userDataDir) { default BrowserContext launchPersistentContext(Path userDataDir) {
@ -1324,11 +1410,20 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this * <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser. * context will automatically close the browser.
* *
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a * @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <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 * href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass * the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
* an empty string to use a temporary directory instead. *
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8 * @since v1.8
*/ */
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options); BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);

View File

@ -174,6 +174,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02"); * page.clock().pauseAt("2020-02-02");
* }</pre> * }</pre>
* *
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at. * @param time Time to pause at.
* @since v1.45 * @since v1.45
*/ */
@ -194,6 +207,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02"); * page.clock().pauseAt("2020-02-02");
* }</pre> * }</pre>
* *
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at. * @param time Time to pause at.
* @since v1.45 * @since v1.45
*/ */
@ -214,6 +240,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02"); * page.clock().pauseAt("2020-02-02");
* }</pre> * }</pre>
* *
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at. * @param time Time to pause at.
* @since v1.45 * @since v1.45
*/ */
@ -227,6 +266,10 @@ public interface Clock {
/** /**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running. * Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
* *
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.clock().setFixedTime(new Date()); * page.clock().setFixedTime(new Date());
@ -241,6 +284,10 @@ public interface Clock {
/** /**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running. * Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
* *
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.clock().setFixedTime(new Date()); * page.clock().setFixedTime(new Date());
@ -255,6 +302,10 @@ public interface Clock {
/** /**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running. * Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
* *
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.clock().setFixedTime(new Date()); * page.clock().setFixedTime(new Date());
@ -267,7 +318,8 @@ public interface Clock {
*/ */
void setFixedTime(Date time); void setFixedTime(Date time);
/** /**
* Sets current system time but does not trigger any timers. * Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
@ -281,7 +333,8 @@ public interface Clock {
*/ */
void setSystemTime(long time); void setSystemTime(long time);
/** /**
* Sets current system time but does not trigger any timers. * Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
@ -295,7 +348,8 @@ public interface Clock {
*/ */
void setSystemTime(String time); void setSystemTime(String time);
/** /**
* Sets current system time but does not trigger any timers. * Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code

View File

@ -39,8 +39,8 @@ import java.util.*;
* }); * });
* *
* // Deconstruct console.log arguments * // Deconstruct console.log arguments
* msg.args().get(0).jsonValue() // hello * msg.args().get(0).jsonValue(); // hello
* msg.args().get(1).jsonValue() // 42 * msg.args().get(1).jsonValue(); // 42
* }</pre> * }</pre>
*/ */
public interface ConsoleMessage { public interface ConsoleMessage {

View File

@ -599,7 +599,9 @@ public interface ElementHandle extends JSHandle {
public ScreenshotCaret caret; public ScreenshotCaret caret;
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public List<Locator> mask; public List<Locator> mask;
/** /**
@ -673,7 +675,9 @@ public interface ElementHandle extends JSHandle {
} }
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public ScreenshotOptions setMask(List<Locator> mask) { public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask; this.mask = mask;
@ -777,7 +781,7 @@ public interface ElementHandle extends JSHandle {
*/ */
public Boolean force; public Boolean force;
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public Boolean noWaitAfter; public Boolean noWaitAfter;
/** /**
@ -797,7 +801,7 @@ public interface ElementHandle extends JSHandle {
return this; return this;
} }
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) { public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter; this.noWaitAfter = noWaitAfter;

View File

@ -290,7 +290,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -375,7 +376,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public ClickOptions setTrial(boolean trial) { public ClickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -426,7 +428,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -504,7 +507,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public DblclickOptions setTrial(boolean trial) { public DblclickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -1127,7 +1131,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -1191,7 +1196,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public HoverOptions setTrial(boolean trial) { public HoverOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -1661,7 +1667,7 @@ public interface Frame {
*/ */
public Boolean force; public Boolean force;
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public Boolean noWaitAfter; public Boolean noWaitAfter;
/** /**
@ -1686,7 +1692,7 @@ public interface Frame {
return this; return this;
} }
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) { public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter; this.noWaitAfter = noWaitAfter;
@ -1932,7 +1938,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -1996,7 +2003,8 @@ public interface Frame {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public TapOptions setTrial(boolean trial) { public TapOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -3491,19 +3499,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3533,19 +3541,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3573,19 +3581,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3615,19 +3623,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>

View File

@ -22,10 +22,10 @@ import java.util.regex.Pattern;
/** /**
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the * 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 * {@code iframe} and locate elements in that iframe. FrameLocator can be created with either {@link
* com.microsoft.playwright.Page#frameLocator Page.frameLocator()} or {@link com.microsoft.playwright.Locator#frameLocator * com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()}, {@link com.microsoft.playwright.Page#frameLocator
* Locator.frameLocator()} method. * Page.frameLocator()} or {@link com.microsoft.playwright.Locator#frameLocator Locator.frameLocator()} method.
* <pre>{@code * <pre>{@code
* Locator locator = page.frameLocator("#my-frame").getByText("Submit"); * Locator locator = page.locator("#my-frame").contentFrame().getByText("Submit");
* locator.click(); * locator.click();
* }</pre> * }</pre>
* *
@ -35,10 +35,10 @@ import java.util.regex.Pattern;
* a given selector. * a given selector.
* <pre>{@code * <pre>{@code
* // Throws if there are several frames in DOM: * // Throws if there are several frames in DOM:
* page.frame_locator(".result-frame").getByRole(AriaRole.BUTTON).click(); * page.locator(".result-frame").contentFrame().getByRole(AriaRole.BUTTON).click();
* *
* // Works because we explicitly tell locator to pick the first frame: * // Works because we explicitly tell locator to pick the first frame:
* page.frame_locator(".result-frame").first().getByRole(AriaRole.BUTTON).click(); * page.locator(".result-frame").first().contentFrame().getByRole(AriaRole.BUTTON).click();
* }</pre> * }</pre>
* *
* <p> <strong>Converting Locator to FrameLocator</strong> * <p> <strong>Converting Locator to FrameLocator</strong>
@ -383,7 +383,8 @@ public interface FrameLocator {
} }
} }
/** /**
* Returns locator to the first matching frame. * @deprecated Use {@link com.microsoft.playwright.Locator#first Locator.first()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* *
* @since v1.17 * @since v1.17
*/ */
@ -733,19 +734,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -775,19 +776,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -815,19 +816,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -857,19 +858,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -953,7 +954,8 @@ public interface FrameLocator {
*/ */
Locator getByTitle(Pattern text, GetByTitleOptions options); Locator getByTitle(Pattern text, GetByTitleOptions options);
/** /**
* Returns locator to the last matching frame. * @deprecated Use {@link com.microsoft.playwright.Locator#last Locator.last()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* *
* @since v1.17 * @since v1.17
*/ */
@ -1003,7 +1005,8 @@ public interface FrameLocator {
*/ */
Locator locator(Locator selectorOrLocator, LocatorOptions options); Locator locator(Locator selectorOrLocator, LocatorOptions options);
/** /**
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame. * @deprecated Use {@link com.microsoft.playwright.Locator#nth Locator.nth()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* *
* @since v1.17 * @since v1.17
*/ */
@ -1018,7 +1021,7 @@ public interface FrameLocator {
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]"); * FrameLocator frameLocator = page.locator("iframe[name=\"embedded\"]").contentFrame();
* // ... * // ...
* Locator locator = frameLocator.owner(); * Locator locator = frameLocator.owner();
* assertThat(locator).isVisible(); * assertThat(locator).isVisible();

View File

@ -48,10 +48,7 @@ import com.microsoft.playwright.options.*;
* *
* <p> An example to trigger select-all with the keyboard * <p> An example to trigger select-all with the keyboard
* <pre>{@code * <pre>{@code
* // on Windows and Linux * page.keyboard().press("ControlOrMeta+A");
* page.keyboard().press("Control+A");
* // on macOS
* page.keyboard().press("Meta+A");
* }</pre> * }</pre>
*/ */
public interface Keyboard { public interface Keyboard {
@ -164,7 +161,7 @@ public interface Keyboard {
* Page page = browser.newPage(); * Page page = browser.newPage();
* page.navigate("https://keycode.info"); * page.navigate("https://keycode.info");
* page.keyboard().press("A"); * page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")); * page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")));
* page.keyboard().press("ArrowLeft"); * page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png"))); * page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O"); * page.keyboard().press("Shift+O");
@ -211,7 +208,7 @@ public interface Keyboard {
* Page page = browser.newPage(); * Page page = browser.newPage();
* page.navigate("https://keycode.info"); * page.navigate("https://keycode.info");
* page.keyboard().press("A"); * page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")); * page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")));
* page.keyboard().press("ArrowLeft"); * page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png"))); * page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O"); * page.keyboard().press("Shift+O");

View File

@ -29,6 +29,26 @@ import java.util.regex.Pattern;
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>. * <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*/ */
public interface Locator { public interface Locator {
class AriaSnapshotOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public AriaSnapshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class BlurOptions { class BlurOptions {
/** /**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
@ -235,7 +255,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -312,7 +333,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public ClickOptions setTrial(boolean trial) { public ClickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -358,7 +380,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -428,7 +451,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public DblclickOptions setTrial(boolean trial) { public DblclickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -576,18 +600,14 @@ public interface Locator {
} }
class EvaluateOptions { class EvaluateOptions {
/** /**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout * itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/ */
public Double timeout; public Double timeout;
/** /**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout * itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/ */
public EvaluateOptions setTimeout(double timeout) { public EvaluateOptions setTimeout(double timeout) {
this.timeout = timeout; this.timeout = timeout;
@ -596,18 +616,14 @@ public interface Locator {
} }
class EvaluateHandleOptions { class EvaluateHandleOptions {
/** /**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout * itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/ */
public Double timeout; public Double timeout;
/** /**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default * Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout * itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/ */
public EvaluateHandleOptions setTimeout(double timeout) { public EvaluateHandleOptions setTimeout(double timeout) {
this.timeout = timeout; this.timeout = timeout;
@ -691,6 +707,10 @@ public interface Locator {
* <article><div>Playwright</div></article>}. * <article><div>Playwright</div></article>}.
*/ */
public Object hasText; public Object hasText;
/**
* Only matches visible or invisible elements.
*/
public Boolean visible;
/** /**
* Narrows down the results of the method to those which contain elements matching this relative locator. For example, * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
@ -753,6 +773,13 @@ public interface Locator {
this.hasText = hasText; this.hasText = hasText;
return this; return this;
} }
/**
* Only matches visible or invisible elements.
*/
public FilterOptions setVisible(boolean visible) {
this.visible = visible;
return this;
}
} }
class FocusOptions { class FocusOptions {
/** /**
@ -1059,7 +1086,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -1115,7 +1143,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public HoverOptions setTrial(boolean trial) { public HoverOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -1493,7 +1522,9 @@ public interface Locator {
public ScreenshotCaret caret; public ScreenshotCaret caret;
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public List<Locator> mask; public List<Locator> mask;
/** /**
@ -1567,7 +1598,9 @@ public interface Locator {
} }
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public ScreenshotOptions setMask(List<Locator> mask) { public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask; this.mask = mask;
@ -1671,7 +1704,7 @@ public interface Locator {
*/ */
public Boolean force; public Boolean force;
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public Boolean noWaitAfter; public Boolean noWaitAfter;
/** /**
@ -1691,7 +1724,7 @@ public interface Locator {
return this; return this;
} }
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) { public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter; this.noWaitAfter = noWaitAfter;
@ -1882,7 +1915,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -1938,7 +1972,8 @@ public interface Locator {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public TapOptions setTrial(boolean trial) { public TapOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -2137,14 +2172,13 @@ public interface Locator {
* When the locator points to a list of elements, this returns an array of locators, pointing to their respective elements. * When the locator points to a list of elements, this returns an array of locators, pointing to their respective elements.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Locator#all Locator.all()} does not wait for elements to match the locator, and instead * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Locator#all Locator.all()} does not wait for elements to match the locator, and instead
* immediately returns whatever is present in the page. When the list of elements changes dynamically, {@link * immediately returns whatever is present in the page.When the list of elements changes dynamically, {@link com.microsoft.playwright.Locator#all Locator.all()} will produce
* com.microsoft.playwright.Locator#all Locator.all()} will produce unpredictable and flaky results. When the list of * unpredictable and flaky results.When the list of elements is stable, but loaded dynamically, wait for the full list to finish loading before calling
* elements is stable, but loaded dynamically, wait for the full list to finish loading before calling {@link * {@link com.microsoft.playwright.Locator#all Locator.all()}.
* com.microsoft.playwright.Locator#all Locator.all()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* for (Locator li : page.getByRole('listitem').all()) * for (Locator li : page.getByRole("listitem").all())
* li.click(); * li.click();
* }</pre> * }</pre>
* *
@ -2195,6 +2229,66 @@ public interface Locator {
* @since v1.34 * @since v1.34
*/ */
Locator and(Locator locator); Locator and(Locator locator);
/**
* Captures the aria snapshot of the given element. Read more about <a
* href="https://playwright.dev/java/docs/aria-snapshots">aria snapshots</a> and {@link
* com.microsoft.playwright.assertions.LocatorAssertions#matchesAriaSnapshot LocatorAssertions.matchesAriaSnapshot()} for
* the corresponding assertion.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.getByRole(AriaRole.LINK).ariaSnapshot();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the
* element and its children. The snapshot can be used to assert the state of the element in the test, or to compare it to
* state in the future.
*
* <p> The ARIA snapshot is represented using <a href="https://yaml.org/spec/1.2.2/">YAML</a> markup language:
* <ul>
* <li> The keys of the objects are the roles and optional accessible names of the elements.</li>
* <li> The values are either text content or an array of child elements.</li>
* <li> Generic static text can be represented with the {@code text} key.</li>
* </ul>
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* @since v1.49
*/
default String ariaSnapshot() {
return ariaSnapshot(null);
}
/**
* Captures the aria snapshot of the given element. Read more about <a
* href="https://playwright.dev/java/docs/aria-snapshots">aria snapshots</a> and {@link
* com.microsoft.playwright.assertions.LocatorAssertions#matchesAriaSnapshot LocatorAssertions.matchesAriaSnapshot()} for
* the corresponding assertion.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.getByRole(AriaRole.LINK).ariaSnapshot();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the
* element and its children. The snapshot can be used to assert the state of the element in the test, or to compare it to
* state in the future.
*
* <p> The ARIA snapshot is represented using <a href="https://yaml.org/spec/1.2.2/">YAML</a> markup language:
* <ul>
* <li> The keys of the objects are the roles and optional accessible names of the elements.</li>
* <li> The values are either text content or an array of child elements.</li>
* <li> Generic static text can be represented with the {@code text} key.</li>
* </ul>
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* @since v1.49
*/
String ariaSnapshot(AriaSnapshotOptions options);
/** /**
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element. * Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
* *
@ -2508,6 +2602,20 @@ public interface Locator {
* @since v1.14 * @since v1.14
*/ */
void dblclick(DblclickOptions options); void dblclick(DblclickOptions options);
/**
* Describes the locator, description is used in the trace viewer and reports. Returns the locator pointing to the same
* element.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByTestId("btn-sub").describe("Subscribe button");
* button.click();
* }</pre>
*
* @param description Locator description.
* @since v1.53
*/
Locator describe(String description);
/** /**
* Programmatically dispatch an event on the matching element. * Programmatically dispatch an event on the matching element.
* *
@ -2543,7 +2651,6 @@ public interface Locator {
* *
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event: * <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code * <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); * JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>(); * Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer); * arg.put("dataTransfer", dataTransfer);
@ -2592,7 +2699,6 @@ public interface Locator {
* *
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event: * <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code * <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); * JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>(); * Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer); * arg.put("dataTransfer", dataTransfer);
@ -2640,7 +2746,6 @@ public interface Locator {
* *
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event: * <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code * <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); * JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>(); * Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer); * arg.put("dataTransfer", dataTransfer);
@ -2756,9 +2861,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws. * <p> If {@code expression} throws or rejects, this method throws.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code * <pre>{@code
* Locator tweets = page.locator(".tweet .retweets"); * Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText")); * " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre> * }</pre>
* *
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@ -2784,9 +2893,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws. * <p> If {@code expression} throws or rejects, this method throws.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code * <pre>{@code
* Locator tweets = page.locator(".tweet .retweets"); * Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText")); * " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre> * }</pre>
* *
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@ -2811,9 +2924,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws. * <p> If {@code expression} throws or rejects, this method throws.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code * <pre>{@code
* Locator tweets = page.locator(".tweet .retweets"); * Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText")); * " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre> * }</pre>
* *
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@ -3448,19 +3565,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3490,19 +3607,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3530,19 +3647,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3572,19 +3689,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -3881,7 +3998,9 @@ public interface Locator {
*/ */
boolean isDisabled(IsDisabledOptions options); boolean isDisabled(IsDisabledOptions options);
/** /**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. * Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. If the
* target element is not an {@code <input>}, {@code <textarea>}, {@code <select>}, {@code [contenteditable]} and does not
* have a role allowing {@code [aria-readonly]}, this method throws an error.
* *
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link * <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link
* com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See * com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See
@ -3898,7 +4017,9 @@ public interface Locator {
return isEditable(null); return isEditable(null);
} }
/** /**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. * Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. If the
* target element is not an {@code <input>}, {@code <textarea>}, {@code <select>}, {@code [contenteditable]} and does not
* have a role allowing {@code [aria-readonly]}, this method throws an error.
* *
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link * <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link
* com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See * com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See
@ -4077,16 +4198,23 @@ public interface Locator {
*/ */
Locator nth(int index); Locator nth(int index);
/** /**
* Creates a locator that matches either of the two locators. * Creates a locator matching all elements that match one or both of the two locators.
*
* <p> Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a <a
* href="https://playwright.dev/java/docs/locators#strictness">locator strictness</a> violation.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
* <p> Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up * <p> Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up
* instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly. * instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly.
*
* <p> <strong>NOTE:</strong> If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, possibly
* throwing the <a href="https://playwright.dev/java/docs/locators#strictness">"strict mode violation" error</a>. In this
* case, you can use {@link com.microsoft.playwright.Locator#first Locator.first()} to only match one of them.
* <pre>{@code * <pre>{@code
* Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New")); * Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
* Locator dialog = page.getByText("Confirm security settings"); * Locator dialog = page.getByText("Confirm security settings");
* assertThat(newEmail.or(dialog)).isVisible(); * assertThat(newEmail.or(dialog).first()).isVisible();
* if (dialog.isVisible()) * if (dialog.isVisible())
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click(); * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
* newEmail.click(); * newEmail.click();
@ -5117,7 +5245,9 @@ public interface Locator {
*/ */
void setInputFiles(FilePayload[] files, SetInputFilesOptions options); void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
/** /**
* Perform a tap gesture on the element matching the locator. * Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
* events</a> page.
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
@ -5143,7 +5273,9 @@ public interface Locator {
tap(null); tap(null);
} }
/** /**
* Perform a tap gesture on the element matching the locator. * Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
* events</a> page.
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *

View File

@ -21,6 +21,11 @@ import com.microsoft.playwright.options.*;
/** /**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport. * The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
* *
* <p> <strong>NOTE:</strong> If you want to debug where the mouse moved, you can use the <a
* href="https://playwright.dev/java/docs/trace-viewer-intro">Trace viewer</a> or <a
* href="https://playwright.dev/java/docs/running-tests">Playwright Inspector</a>. A red dot showing the location of the
* mouse will be shown for every mouse action.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}. * <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}.
* <pre>{@code * <pre>{@code
* // Using page.mouse to trace a 100x100 square. * // Using page.mouse to trace a 100x100 square.

View File

@ -560,7 +560,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -645,7 +646,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public ClickOptions setTrial(boolean trial) { public ClickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -723,7 +725,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -801,7 +804,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public DblclickOptions setTrial(boolean trial) { public DblclickOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -955,10 +959,17 @@ public interface Page extends AutoCloseable {
} }
class EmulateMediaOptions { class EmulateMediaOptions {
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. Passing {@code null} disables color scheme emulation. * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. Passing {@code null} disables color scheme emulation.
* {@code "no-preference"} is deprecated.
*/ */
public Optional<ColorScheme> colorScheme; public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing
* {@code null} disables contrast emulation.
*/
public Optional<Contrast> contrast;
/** /**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code
* null} disables forced colors emulation. * null} disables forced colors emulation.
@ -976,13 +987,23 @@ public interface Page extends AutoCloseable {
public Optional<ReducedMotion> reducedMotion; public Optional<ReducedMotion> reducedMotion;
/** /**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code * Emulates <a
* "no-preference"}. Passing {@code null} disables color scheme emulation. * href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. Passing {@code null} disables color scheme emulation.
* {@code "no-preference"} is deprecated.
*/ */
public EmulateMediaOptions setColorScheme(ColorScheme colorScheme) { public EmulateMediaOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme); this.colorScheme = Optional.ofNullable(colorScheme);
return this; return this;
} }
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing
* {@code null} disables contrast emulation.
*/
public EmulateMediaOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/** /**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code
* null} disables forced colors emulation. * null} disables forced colors emulation.
@ -1591,7 +1612,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -1655,7 +1677,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public HoverOptions setTrial(boolean trial) { public HoverOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -2421,7 +2444,7 @@ public interface Page extends AutoCloseable {
/** /**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, * 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 * cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}. * minimal}.
*/ */
public HarMode updateMode; public HarMode updateMode;
/** /**
@ -2461,7 +2484,7 @@ public interface Page extends AutoCloseable {
/** /**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, * 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 * cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}. * minimal}.
*/ */
public RouteFromHAROptions setUpdateMode(HarMode updateMode) { public RouteFromHAROptions setUpdateMode(HarMode updateMode) {
this.updateMode = updateMode; this.updateMode = updateMode;
@ -2512,7 +2535,9 @@ public interface Page extends AutoCloseable {
public Boolean fullPage; public Boolean fullPage;
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public List<Locator> mask; public List<Locator> mask;
/** /**
@ -2607,7 +2632,9 @@ public interface Page extends AutoCloseable {
} }
/** /**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/ */
public ScreenshotOptions setMask(List<Locator> mask) { public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask; this.mask = mask;
@ -2691,7 +2718,7 @@ public interface Page extends AutoCloseable {
*/ */
public Boolean force; public Boolean force;
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public Boolean noWaitAfter; public Boolean noWaitAfter;
/** /**
@ -2716,7 +2743,7 @@ public interface Page extends AutoCloseable {
return this; return this;
} }
/** /**
* @deprecated This option will default to {@code true} in the future. * @deprecated This option has no effect.
*/ */
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) { public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter; this.noWaitAfter = noWaitAfter;
@ -2962,7 +2989,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public Boolean trial; public Boolean trial;
@ -3026,7 +3054,8 @@ public interface Page extends AutoCloseable {
/** /**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a> * When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without * checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. * performing it. Note that keyboard {@code modifiers} will be pressed regardless of {@code trial} to allow testing
* elements which are only visible when those keys are pressed.
*/ */
public TapOptions setTrial(boolean trial) { public TapOptions setTrial(boolean trial) {
this.trial = trial; this.trial = trial;
@ -4143,9 +4172,9 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.dragAndDrop("#source", '#target'); * page.dragAndDrop("#source", "#target");
* // or specify exact positions relative to the top-left corners of the elements: * // or specify exact positions relative to the top-left corners of the elements:
* page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions() * page.dragAndDrop("#source", "#target", new Page.DragAndDropOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20)); * .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre> * }</pre>
* *
@ -4164,9 +4193,9 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.dragAndDrop("#source", '#target'); * page.dragAndDrop("#source", "#target");
* // or specify exact positions relative to the top-left corners of the elements: * // or specify exact positions relative to the top-left corners of the elements:
* page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions() * page.dragAndDrop("#source", "#target", new Page.DragAndDropOptions()
* .setSourcePosition(34, 7).setTargetPosition(10, 20)); * .setSourcePosition(34, 7).setTargetPosition(10, 20));
* }</pre> * }</pre>
* *
@ -4206,8 +4235,6 @@ public interface Page extends AutoCloseable {
* // true * // true
* page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"); * page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches");
* // false * // false
* page.evaluate("() => matchMedia('(prefers-color-scheme: no-preference)').matches");
* // false
* }</pre> * }</pre>
* *
* @since v1.8 * @since v1.8
@ -4244,8 +4271,6 @@ public interface Page extends AutoCloseable {
* // true * // true
* page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"); * page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches");
* // false * // false
* page.evaluate("() => matchMedia('(prefers-color-scheme: no-preference)').matches");
* // false
* }</pre> * }</pre>
* *
* @since v1.8 * @since v1.8
@ -4552,7 +4577,7 @@ public interface Page extends AutoCloseable {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit(); * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch({ headless: false }); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* Page page = context.newPage(); * Page page = context.newPage();
* page.exposeBinding("pageURL", (source, args) -> source.page().url()); * page.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -4602,7 +4627,7 @@ public interface Page extends AutoCloseable {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit(); * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch({ headless: false }); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext(); * BrowserContext context = browser.newContext();
* Page page = context.newPage(); * Page page = context.newPage();
* page.exposeBinding("pageURL", (source, args) -> source.page().url()); * page.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -4654,26 +4679,27 @@ public interface Page extends AutoCloseable {
* public static void main(String[] args) { * public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) { * try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit(); * BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch({ headless: false }); * Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* Page page = browser.newPage(); * Page page = browser.newPage();
* page.exposeFunction("sha256", args -> { * page.exposeFunction("sha256", args -> {
* String text = (String) args[0];
* MessageDigest crypto;
* try { * try {
* crypto = MessageDigest.getInstance("SHA-256"); * String text = (String) args[0];
* MessageDigest crypto = MessageDigest.getInstance("SHA-256");
* byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
* return Base64.getEncoder().encodeToString(token);
* } catch (NoSuchAlgorithmException e) { * } catch (NoSuchAlgorithmException e) {
* return null; * return null;
* } * }
* byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
* return Base64.getEncoder().encodeToString(token);
* }); * });
* page.setContent("<script>\n" + * page.setContent(
* "<script>\n" +
* " async function onClick() {\n" + * " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" + * " document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" +
* " }\n" + * " }\n" +
* "</script>\n" + * "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" + * "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>\n"); * "<div></div>"
* );
* page.click("button"); * page.click("button");
* } * }
* } * }
@ -4749,7 +4775,7 @@ public interface Page extends AutoCloseable {
* Frame frame = page.frame("frame-name"); * Frame frame = page.frame("frame-name");
* }</pre> * }</pre>
* <pre>{@code * <pre>{@code
* Frame frame = page.frameByUrl(Pattern.compile(".*domain.*"); * Frame frame = page.frameByUrl(Pattern.compile(".*domain.*"));
* }</pre> * }</pre>
* *
* @param name Frame name specified in the {@code iframe}'s {@code name} attribute. * @param name Frame name specified in the {@code iframe}'s {@code name} attribute.
@ -5155,19 +5181,19 @@ public interface Page extends AutoCloseable {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -5197,19 +5223,19 @@ public interface Page extends AutoCloseable {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -5237,19 +5263,19 @@ public interface Page extends AutoCloseable {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -5279,19 +5305,19 @@ public interface Page extends AutoCloseable {
* <p> You can locate by text substring, exact string, or a regular expression: * <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code * <pre>{@code
* // Matches <span> * // Matches <span>
* page.getByText("world") * page.getByText("world");
* *
* // Matches first <div> * // Matches first <div>
* page.getByText("Hello world") * page.getByText("Hello world");
* *
* // Matches second <div> * // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) * page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* *
* // Matches both <div>s * // Matches both <div>s
* page.getByText(Pattern.compile("Hello")) * page.getByText(Pattern.compile("Hello"));
* *
* // Matches second <div> * // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) * page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre> * }</pre>
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
@ -5414,6 +5440,25 @@ public interface Page extends AutoCloseable {
* @since v1.8 * @since v1.8
*/ */
Response goForward(GoForwardOptions options); Response goForward(GoForwardOptions options);
/**
* Request the page to perform garbage collection. Note that there is no guarantee that all unreachable objects will be
* collected.
*
* <p> This is useful to help detect memory leaks. For example, if your page has a large object {@code "suspect"} that might be
* leaked, you can check that it does not leak by using a <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef">{@code WeakRef}</a>.
* <pre>{@code
* // 1. In your page, save a WeakRef for the "suspect".
* page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)");
* // 2. Request garbage collection.
* page.requestGC();
* // 3. Check that weak ref does not deref to the original object.
* assertTrue(page.evaluate("!globalThis.suspectWeakRef.deref()"));
* }</pre>
*
* @since v1.48
*/
void requestGC();
/** /**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the first * Returns the main resource response. In case of multiple redirects, the navigation will resolve with the first
* non-redirect response. * non-redirect response.
@ -5767,14 +5812,13 @@ public interface Page extends AutoCloseable {
*/ */
Page opener(); Page opener();
/** /**
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' button * Pauses script execution. Playwright will stop executing the script and wait for the user to either press the 'Resume'
* in the page overlay or to call {@code playwright.resume()} in the DevTools console. * button in the page overlay or to call {@code playwright.resume()} in the DevTools console.
* *
* <p> User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from * <p> User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
* the place it was paused. * the place it was paused.
* *
* <p> <strong>NOTE:</strong> This method requires Playwright to be started in a headed mode, with a falsy {@code headless} value in the {@link * <p> <strong>NOTE:</strong> This method requires Playwright to be started in a headed mode, with a falsy {@code headless} option.
* com.microsoft.playwright.BrowserType#launch BrowserType.launch()}.
* *
* @since v1.9 * @since v1.9
*/ */
@ -5782,8 +5826,6 @@ public interface Page extends AutoCloseable {
/** /**
* Returns the PDF buffer. * Returns the PDF buffer.
* *
* <p> <strong>NOTE:</strong> Generating a pdf is currently only supported in Chromium headless.
*
* <p> {@code page.pdf()} generates a pdf of the page with {@code print} css media. To generate a pdf with {@code screen} * <p> {@code page.pdf()} generates a pdf of the page with {@code print} css media. To generate a pdf with {@code screen}
* media, call {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} before calling {@code page.pdf()}: * media, call {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} before calling {@code page.pdf()}:
* *
@ -5842,8 +5884,6 @@ public interface Page extends AutoCloseable {
/** /**
* Returns the PDF buffer. * Returns the PDF buffer.
* *
* <p> <strong>NOTE:</strong> Generating a pdf is currently only supported in Chromium headless.
*
* <p> {@code page.pdf()} generates a pdf of the page with {@code print} css media. To generate a pdf with {@code screen} * <p> {@code page.pdf()} generates a pdf of the page with {@code print} css media. To generate a pdf with {@code screen}
* media, call {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} before calling {@code page.pdf()}: * media, call {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} before calling {@code page.pdf()}:
* *
@ -6041,39 +6081,37 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Running the handler will alter your page state mid-test. For example it will change the currently focused element and * <p> <strong>NOTE:</strong> Running the handler will alter your page state mid-test. For example it will change the currently focused element and
* move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and * move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and
* mouse state being unchanged. <br /> <br /> For example, consider a test that calls {@link * mouse state being unchanged.For example, consider a test that calls {@link com.microsoft.playwright.Locator#focus Locator.focus()} followed by
* com.microsoft.playwright.Locator#focus Locator.focus()} followed by {@link com.microsoft.playwright.Keyboard#press * {@link com.microsoft.playwright.Keyboard#press Keyboard.press()}. If your handler clicks a button between these two
* Keyboard.press()}. If your handler clicks a button between these two actions, the focused element most likely will be * actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use {@link
* wrong, and key press will happen on the unexpected element. Use {@link com.microsoft.playwright.Locator#press * com.microsoft.playwright.Locator#press Locator.press()} instead to avoid this problem.Another example is a series of mouse actions, where {@link com.microsoft.playwright.Mouse#move Mouse.move()} is followed
* Locator.press()} instead to avoid this problem. <br /> <br /> Another example is a series of mouse actions, where {@link * by {@link com.microsoft.playwright.Mouse#down Mouse.down()}. Again, when the handler runs between these two actions, the
* com.microsoft.playwright.Mouse#move Mouse.move()} is followed by {@link com.microsoft.playwright.Mouse#down * mouse position will be wrong during the mouse down. Prefer self-contained actions like {@link
* Mouse.down()}. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse * com.microsoft.playwright.Locator#click Locator.click()} that do not rely on the state being unchanged by a handler.
* down. Prefer self-contained actions like {@link com.microsoft.playwright.Locator#click Locator.click()} that do not rely
* on the state being unchanged by a handler.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
* <p> An example that closes a "Sign up to the newsletter" dialog when it appears: * <p> An example that closes a "Sign up to the newsletter" dialog when it appears:
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => { * page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () -> {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click(); * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
* }); * });
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
* <p> An example that skips the "Confirm your security details" page when it is shown: * <p> An example that skips the "Confirm your security details" page when it is shown:
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.getByText("Confirm your security details")), () => { * page.addLocatorHandler(page.getByText("Confirm your security details"), () -> {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click(); * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
* }); * });
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
@ -6082,19 +6120,19 @@ public interface Page extends AutoCloseable {
* handler does not hide the {@code <body>} element. * handler does not hide the {@code <body>} element.
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.locator("body")), () => { * page.addLocatorHandler(page.locator("body"), () -> {
* page.evaluate("window.removeObstructionsForTestIfNeeded()"); * page.evaluate("window.removeObstructionsForTestIfNeeded()");
* }, new Page.AddLocatorHandlerOptions.setNoWaitAfter(true)); * }, new Page.AddLocatorHandlerOptions().setNoWaitAfter(true));
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
* <p> Handler takes the original locator as an argument. You can also automatically remove the handler after a number of * <p> Handler takes the original locator as an argument. You can also automatically remove the handler after a number of
* invocations by setting {@code times}: * invocations by setting {@code times}:
* <pre>{@code * <pre>{@code
* page.addLocatorHandler(page.getByLabel("Close"), locator => { * page.addLocatorHandler(page.getByLabel("Close"), locator -> {
* locator.click(); * locator.click();
* }, new Page.AddLocatorHandlerOptions().setTimes(1)); * }, new Page.AddLocatorHandlerOptions().setTimes(1));
* }</pre> * }</pre>
@ -6135,39 +6173,37 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Running the handler will alter your page state mid-test. For example it will change the currently focused element and * <p> <strong>NOTE:</strong> Running the handler will alter your page state mid-test. For example it will change the currently focused element and
* move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and * move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and
* mouse state being unchanged. <br /> <br /> For example, consider a test that calls {@link * mouse state being unchanged.For example, consider a test that calls {@link com.microsoft.playwright.Locator#focus Locator.focus()} followed by
* com.microsoft.playwright.Locator#focus Locator.focus()} followed by {@link com.microsoft.playwright.Keyboard#press * {@link com.microsoft.playwright.Keyboard#press Keyboard.press()}. If your handler clicks a button between these two
* Keyboard.press()}. If your handler clicks a button between these two actions, the focused element most likely will be * actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use {@link
* wrong, and key press will happen on the unexpected element. Use {@link com.microsoft.playwright.Locator#press * com.microsoft.playwright.Locator#press Locator.press()} instead to avoid this problem.Another example is a series of mouse actions, where {@link com.microsoft.playwright.Mouse#move Mouse.move()} is followed
* Locator.press()} instead to avoid this problem. <br /> <br /> Another example is a series of mouse actions, where {@link * by {@link com.microsoft.playwright.Mouse#down Mouse.down()}. Again, when the handler runs between these two actions, the
* com.microsoft.playwright.Mouse#move Mouse.move()} is followed by {@link com.microsoft.playwright.Mouse#down * mouse position will be wrong during the mouse down. Prefer self-contained actions like {@link
* Mouse.down()}. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse * com.microsoft.playwright.Locator#click Locator.click()} that do not rely on the state being unchanged by a handler.
* down. Prefer self-contained actions like {@link com.microsoft.playwright.Locator#click Locator.click()} that do not rely
* on the state being unchanged by a handler.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* *
* <p> An example that closes a "Sign up to the newsletter" dialog when it appears: * <p> An example that closes a "Sign up to the newsletter" dialog when it appears:
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => { * page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () -> {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click(); * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
* }); * });
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
* <p> An example that skips the "Confirm your security details" page when it is shown: * <p> An example that skips the "Confirm your security details" page when it is shown:
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.getByText("Confirm your security details")), () => { * page.addLocatorHandler(page.getByText("Confirm your security details"), () -> {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click(); * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
* }); * });
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
@ -6176,19 +6212,19 @@ public interface Page extends AutoCloseable {
* handler does not hide the {@code <body>} element. * handler does not hide the {@code <body>} element.
* <pre>{@code * <pre>{@code
* // Setup the handler. * // Setup the handler.
* page.addLocatorHandler(page.locator("body")), () => { * page.addLocatorHandler(page.locator("body"), () -> {
* page.evaluate("window.removeObstructionsForTestIfNeeded()"); * page.evaluate("window.removeObstructionsForTestIfNeeded()");
* }, new Page.AddLocatorHandlerOptions.setNoWaitAfter(true)); * }, new Page.AddLocatorHandlerOptions().setNoWaitAfter(true));
* *
* // Write the test as usual. * // Write the test as usual.
* page.goto("https://example.com"); * page.navigate("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); * page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre> * }</pre>
* *
* <p> Handler takes the original locator as an argument. You can also automatically remove the handler after a number of * <p> Handler takes the original locator as an argument. You can also automatically remove the handler after a number of
* invocations by setting {@code times}: * invocations by setting {@code times}:
* <pre>{@code * <pre>{@code
* page.addLocatorHandler(page.getByLabel("Close"), locator => { * page.addLocatorHandler(page.getByLabel("Close"), locator -> {
* locator.click(); * locator.click();
* }, new Page.AddLocatorHandlerOptions().setTimes(1)); * }, new Page.AddLocatorHandlerOptions().setTimes(1));
* }</pre> * }</pre>
@ -6240,7 +6276,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6281,8 +6317,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6299,7 +6335,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6340,8 +6376,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6356,7 +6392,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6397,8 +6433,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6415,7 +6451,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6456,8 +6492,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6472,7 +6508,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6513,8 +6549,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6531,7 +6567,7 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept requests intercepted by Service Worker. See
* <a href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers * <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"}. * when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* *
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#route Page.route()} will not intercept the first request of a popup page. Use
* {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead. * {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} instead.
@ -6572,8 +6608,8 @@ public interface Page extends AutoCloseable {
* *
* <p> <strong>NOTE:</strong> Enabling routing disables http cache. * <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 * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* context options was provided and the passed URL is a path, it gets merged via the <a * the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor. * 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. * @param handler handler function to route the request.
* @since v1.8 * @since v1.8
@ -6585,7 +6621,7 @@ public interface Page extends AutoCloseable {
* *
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <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 * 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"}. * using request interception by setting {@code 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 * @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. * path} is a relative path, then it is resolved relative to the current working directory.
@ -6600,13 +6636,88 @@ public interface Page extends AutoCloseable {
* *
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <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 * 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"}. * using request interception by setting {@code 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 * @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. * path} is a relative path, then it is resolved relative to the current working directory.
* @since v1.23 * @since v1.23
*/ */
void routeFromHAR(Path har, RouteFromHAROptions options); void routeFromHAR(Path har, RouteFromHAROptions options);
/**
* This method allows to modify websocket connections that are made by the page.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before navigating the page.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple mock that responds to a single message. See {@code WebSocketRoute} for more details and
* examples.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(String url, Consumer<WebSocketRoute> handler);
/**
* This method allows to modify websocket connections that are made by the page.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before navigating the page.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple mock that responds to a single message. See {@code WebSocketRoute} for more details and
* examples.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(Pattern url, Consumer<WebSocketRoute> handler);
/**
* This method allows to modify websocket connections that are made by the page.
*
* <p> Note that only {@code WebSocket}s created after this method was called will be routed. It is recommended to call this
* method before navigating the page.
*
* <p> <strong>Usage</strong>
*
* <p> Below is an example of a simple mock that responds to a single message. See {@code WebSocketRoute} for more details and
* examples.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
* }</pre>
*
* @param url Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the {@code
* baseURL} context option.
* @param handler Handler function to route the WebSocket.
* @since v1.48
*/
void routeWebSocket(Predicate<String> url, Consumer<WebSocketRoute> handler);
/** /**
* Returns the buffer with the captured screenshot. * Returns the buffer with the captured screenshot.
* *
@ -7096,7 +7207,7 @@ public interface Page extends AutoCloseable {
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#setDefaultNavigationTimeout Page.setDefaultNavigationTimeout()} takes priority over * <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#setDefaultNavigationTimeout Page.setDefaultNavigationTimeout()} takes priority over
* {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}. * {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}.
* *
* @param timeout Maximum time in milliseconds * @param timeout Maximum time in milliseconds. Pass {@code 0} to disable timeout.
* @since v1.8 * @since v1.8
*/ */
void setDefaultTimeout(double timeout); void setDefaultTimeout(double timeout);
@ -7256,6 +7367,8 @@ public interface Page extends AutoCloseable {
* page.navigate("https://example.com"); * page.navigate("https://example.com");
* }</pre> * }</pre>
* *
* @param width Page width in pixels.
* @param height Page height in pixels.
* @since v1.8 * @since v1.8
*/ */
void setViewportSize(int width, int height); void setViewportSize(int width, int height);

View File

@ -348,7 +348,7 @@ public interface Route {
*/ */
void abort(String errorCode); void abort(String errorCode);
/** /**
* Continues route's request with optional overrides. * Sends route's request to the network with optional overrides.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
@ -363,10 +363,16 @@ public interface Route {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request * <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header * {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link *
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead. * <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
* *
* @since v1.8 * @since v1.8
*/ */
@ -374,7 +380,7 @@ public interface Route {
resume(null); resume(null);
} }
/** /**
* Continues route's request with optional overrides. * Sends route's request to the network with optional overrides.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
@ -389,21 +395,30 @@ public interface Route {
* *
* <p> <strong>Details</strong> * <p> <strong>Details</strong>
* *
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request * <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header * {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link *
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead. * <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
* *
* @since v1.8 * @since v1.8
*/ */
void resume(ResumeOptions options); void resume(ResumeOptions options);
/** /**
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last * Continues route's request with optional overrides. The method is similar to {@link com.microsoft.playwright.Route#resume
* Route.resume()} with the difference that other matching handlers will be invoked before sending the request.
*
* <p> <strong>Usage</strong>
*
* <p> 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 * 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 * 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. * registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.route("**\/*", route -> { * page.route("**\/*", route -> {
* // Runs last. * // Runs last.
@ -457,18 +472,24 @@ public interface Route {
* }); * });
* }</pre> * }</pre>
* *
* <p> Use {@link com.microsoft.playwright.Route#resume Route.resume()} to immediately send the request to the network, other
* matching handlers won't be invoked in that case.
*
* @since v1.23 * @since v1.23
*/ */
default void fallback() { default void fallback() {
fallback(null); fallback(null);
} }
/** /**
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last * Continues route's request with optional overrides. The method is similar to {@link com.microsoft.playwright.Route#resume
* Route.resume()} with the difference that other matching handlers will be invoked before sending the request.
*
* <p> <strong>Usage</strong>
*
* <p> 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 * 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 * 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. * registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* page.route("**\/*", route -> { * page.route("**\/*", route -> {
* // Runs last. * // Runs last.
@ -522,6 +543,9 @@ public interface Route {
* }); * });
* }</pre> * }</pre>
* *
* <p> Use {@link com.microsoft.playwright.Route#resume Route.resume()} to immediately send the request to the network, other
* matching handlers won't be invoked in that case.
*
* @since v1.23 * @since v1.23
*/ */
void fallback(FallbackOptions options); void fallback(FallbackOptions options);

View File

@ -20,6 +20,9 @@ package com.microsoft.playwright;
/** /**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the * The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
* touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true. * touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true.
*
* <p> This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch
* events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch events</a> page.
*/ */
public interface Touchscreen { public interface Touchscreen {
/** /**

View File

@ -16,12 +16,19 @@
package com.microsoft.playwright; package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path; import java.nio.file.Path;
/** /**
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a * 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. * href="https://playwright.dev/java/docs/trace-viewer">Trace Viewer</a> after Playwright script runs.
* *
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code context.tracing}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file. * <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code * <pre>{@code
* Browser browser = chromium.launch(); * Browser browser = chromium.launch();
@ -39,8 +46,8 @@ public interface Tracing {
class StartOptions { class StartOptions {
/** /**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code * If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the * tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop * the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead. * Tracing.stop()} instead.
*/ */
public String name; public String name;
@ -69,8 +76,8 @@ public interface Tracing {
/** /**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code * If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the * tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop * the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead. * Tracing.stop()} instead.
*/ */
public StartOptions setName(String name) { public StartOptions setName(String name) {
@ -115,8 +122,8 @@ public interface Tracing {
class StartChunkOptions { class StartChunkOptions {
/** /**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code * If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the * tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk * the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead. * Tracing.stopChunk()} instead.
*/ */
public String name; public String name;
@ -127,8 +134,8 @@ public interface Tracing {
/** /**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code * If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the * tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk * the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead. * Tracing.stopChunk()} instead.
*/ */
public StartChunkOptions setName(String name) { public StartChunkOptions setName(String name) {
@ -143,6 +150,29 @@ public interface Tracing {
return this; return this;
} }
} }
class GroupOptions {
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public Location location;
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(String file) {
return setLocation(new Location(file));
}
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(Location location) {
this.location = location;
return this;
}
}
class StopOptions { class StopOptions {
/** /**
* Export trace into the file with the given path. * Export trace into the file with the given path.
@ -176,6 +206,12 @@ public interface Tracing {
/** /**
* Start tracing. * Start tracing.
* *
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* context.tracing().start(new Tracing.StartOptions() * context.tracing().start(new Tracing.StartOptions()
@ -195,6 +231,12 @@ public interface Tracing {
/** /**
* Start tracing. * Start tracing.
* *
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* context.tracing().start(new Tracing.StartOptions() * context.tracing().start(new Tracing.StartOptions()
@ -271,6 +313,56 @@ public interface Tracing {
* @since v1.15 * @since v1.15
*/ */
void startChunk(StartChunkOptions options); void startChunk(StartChunkOptions options);
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
default void group(String name) {
group(name, null);
}
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
void group(String name, GroupOptions options);
/**
* Closes the last group created by {@link com.microsoft.playwright.Tracing#group Tracing.group()}.
*
* @since v1.49
*/
void groupEnd();
/** /**
* Stop tracing. * Stop tracing.
* *

View File

@ -20,7 +20,10 @@ import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
/** /**
* The {@code WebSocket} class represents websocket connections in the page. * The {@code WebSocket} class represents WebSocket connections within a page. It provides the ability to inspect and
* manipulate the data being transmitted and received.
*
* <p> If you want to intercept or modify WebSocket frames, consider using {@code WebSocketRoute}.
*/ */
public interface WebSocket { public interface WebSocket {

View File

@ -0,0 +1,223 @@
/*
* 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.function.BiConsumer;
import java.util.function.Consumer;
/**
* Whenever a <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">{@code WebSocket}</a> route is set up
* with {@link com.microsoft.playwright.Page#routeWebSocket Page.routeWebSocket()} or {@link
* com.microsoft.playwright.BrowserContext#routeWebSocket BrowserContext.routeWebSocket()}, the {@code WebSocketRoute}
* object allows to handle the WebSocket, like an actual server would do.
*
* <p> <strong>Mocking</strong>
*
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over the
* WebSocket. Here is an example that responds to a {@code "request"} with a {@code "response"}.
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
* }</pre>
*
* <p> Since we do not call {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}
* inside the WebSocket route handler, Playwright assumes that WebSocket will be mocked, and opens the WebSocket inside the
* page automatically.
*
* <p> Here is another example that handles JSON messages:
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(frame -> {
* JsonObject json = new JsonParser().parse(frame.text()).getAsJsonObject();
* if ("question".equals(json.get("request").getAsString())) {
* Map<String, String> result = new HashMap();
* result.put("response", "answer");
* ws.send(gson.toJson(result));
* }
* });
* });
* }</pre>
*
* <p> <strong>Intercepting</strong>
*
* <p> Alternatively, you may want to connect to the actual server, but intercept messages in-between and modify or block them.
* Calling {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()} returns a
* server-side {@code WebSocketRoute} instance that you can send messages to, or handle incoming messages.
*
* <p> Below is an example that modifies some messages sent by the page to the server. Messages sent from the server to the
* page are left intact, relying on the default forwarding.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* server.send("request2");
* else
* server.send(frame.text());
* });
* });
* }</pre>
*
* <p> After connecting to the server, all **messages are forwarded** between the page and the server by default.
*
* <p> However, if you call {@link com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} on the
* original route, messages from the page to the server **will not be forwarded** anymore, but should instead be handled by
* the {@code handler}.
*
* <p> Similarly, calling {@link com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} on the
* server-side WebSocket will **stop forwarding messages** from the server to the page, and {@code handler} should take
* care of them.
*
* <p> The following example blocks some messages in both directions. Since it calls {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} in both directions, there is no automatic
* forwarding at all.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if (!"blocked-from-the-page".equals(frame.text()))
* server.send(frame.text());
* });
* server.onMessage(frame -> {
* if (!"blocked-from-the-server".equals(frame.text()))
* ws.send(frame.text());
* });
* });
* }</pre>
*/
public interface WebSocketRoute {
class CloseOptions {
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a>.
*/
public Integer code;
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
*/
public String reason;
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a>.
*/
public CloseOptions setCode(int code) {
this.code = code;
return this;
}
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
/**
* Closes one side of the WebSocket connection.
*
* @since v1.48
*/
default void close() {
close(null);
}
/**
* Closes one side of the WebSocket connection.
*
* @since v1.48
*/
void close(CloseOptions options);
/**
* By default, routed WebSocket does not connect to the server, so you can mock entire WebSocket communication. This method
* connects to the actual WebSocket server, and returns the server-side {@code WebSocketRoute} instance, giving the ability
* to send and receive messages from the server.
*
* <p> Once connected to the server:
* <ul>
* <li> Messages received from the server will be **automatically forwarded** to the WebSocket in the page, unless {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} is called on the server-side {@code
* WebSocketRoute}.</li>
* <li> Messages sent by the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send">{@code
* WebSocket.send()}</a> call in the page will be **automatically forwarded** to the server, unless {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} is called on the original {@code
* WebSocketRoute}.</li>
* </ul>
*
* <p> See examples at the top for more details.
*
* @since v1.48
*/
WebSocketRoute connectToServer();
/**
* Allows to handle <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close">{@code WebSocket.close}</a>.
*
* <p> By default, closing one side of the connection, either in the page or on the server, will close the other side. However,
* when {@link com.microsoft.playwright.WebSocketRoute#onClose WebSocketRoute.onClose()} handler is set up, the default
* forwarding of closure is disabled, and handler should take care of it.
*
* @param handler Function that will handle WebSocket closure. Received an optional <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a> and an optional <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
* @since v1.48
*/
void onClose(BiConsumer<Integer, String> handler);
/**
* This method allows to handle messages that are sent by the WebSocket, either from the page or from the server.
*
* <p> When called on the original WebSocket route, this method handles messages sent from the page. You can handle this
* messages by responding to them with {@link com.microsoft.playwright.WebSocketRoute#send WebSocketRoute.send()},
* forwarding them to the server-side connection returned by {@link com.microsoft.playwright.WebSocketRoute#connectToServer
* WebSocketRoute.connectToServer()} or do something else.
*
* <p> Once this method is called, messages are not automatically forwarded to the server or to the page - you should do that
* manually by calling {@link com.microsoft.playwright.WebSocketRoute#send WebSocketRoute.send()}. See examples at the top
* for more details.
*
* <p> Calling this method again will override the handler with a new one.
*
* @param handler Function that will handle messages.
* @since v1.48
*/
void onMessage(Consumer<WebSocketFrame> handler);
/**
* Sends a message to the WebSocket. When called on the original WebSocket, sends the message to the page. When called on
* the result of {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}, sends
* the message to the server. See examples at the top for more details.
*
* @param message Message to send.
* @since v1.48
*/
void send(String message);
/**
* Sends a message to the WebSocket. When called on the original WebSocket, sends the message to the page. When called on
* the result of {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}, sends
* the message to the server. See examples at the top for more details.
*
* @param message Message to send.
* @since v1.48
*/
void send(byte[] message);
/**
* URL of the WebSocket created in the page.
*
* @since v1.48
*/
String url();
}

View File

@ -21,15 +21,15 @@ package com.microsoft.playwright.assertions;
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code * The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code
* APIResponse} in the tests. * APIResponse} in the tests.
* <pre>{@code * <pre>{@code
* ... * // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
* *
* public class TestPage { * public class TestPage {
* ... * // ...
* @Test * @Test
* void navigatesToLoginPage() { * void navigatesToLoginPage() {
* ... * // ...
* APIResponse response = page.request().get('https://playwright.dev'); * APIResponse response = page.request().get("https://playwright.dev");
* assertThat(response).isOK(); * assertThat(response).isOK();
* } * }
* } * }

View File

@ -16,6 +16,7 @@
package com.microsoft.playwright.assertions; package com.microsoft.playwright.assertions;
import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.microsoft.playwright.options.AriaRole; import com.microsoft.playwright.options.AriaRole;
@ -23,14 +24,14 @@ import com.microsoft.playwright.options.AriaRole;
* The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code * The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code
* Locator} state in the tests. * Locator} state in the tests.
* <pre>{@code * <pre>{@code
* ... * // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
* *
* public class TestLocator { * public class TestLocator {
* ... * // ...
* @Test * @Test
* void statusBecomesSubmitted() { * void statusBecomesSubmitted() {
* ... * // ...
* page.getByRole(AriaRole.BUTTON).click(); * page.getByRole(AriaRole.BUTTON).click();
* assertThat(page.locator(".status")).hasText("Submitted"); * assertThat(page.locator(".status")).hasText("Submitted");
* } * }
@ -58,16 +59,37 @@ public interface LocatorAssertions {
} }
} }
class IsCheckedOptions { class IsCheckedOptions {
/**
* Provides state to assert for. Asserts for input to be checked by default. This option can't be used when {@code
* indeterminate} is set to true.
*/
public Boolean checked; public Boolean checked;
/**
* Asserts that the element is in the indeterminate (mixed) state. Only supported for checkboxes and radio buttons. This
* option can't be true when {@code checked} is provided.
*/
public Boolean indeterminate;
/** /**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}. * Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/ */
public Double timeout; public Double timeout;
/**
* Provides state to assert for. Asserts for input to be checked by default. This option can't be used when {@code
* indeterminate} is set to true.
*/
public IsCheckedOptions setChecked(boolean checked) { public IsCheckedOptions setChecked(boolean checked) {
this.checked = checked; this.checked = checked;
return this; return this;
} }
/**
* Asserts that the element is in the indeterminate (mixed) state. Only supported for checkboxes and radio buttons. This
* option can't be true when {@code checked} is provided.
*/
public IsCheckedOptions setIndeterminate(boolean indeterminate) {
this.indeterminate = indeterminate;
return this;
}
/** /**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}. * Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/ */
@ -216,6 +238,20 @@ public interface LocatorAssertions {
return this; return this;
} }
} }
class ContainsClassOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public ContainsClassOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ContainsTextOptions { class ContainsTextOptions {
/** /**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@ -281,6 +317,33 @@ public interface LocatorAssertions {
return this; return this;
} }
} }
class HasAccessibleErrorMessageOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public HasAccessibleErrorMessageOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasAccessibleErrorMessageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasAccessibleNameOptions { class HasAccessibleNameOptions {
/** /**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@ -485,6 +548,20 @@ public interface LocatorAssertions {
return this; return this;
} }
} }
class MatchesAriaSnapshotOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public MatchesAriaSnapshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/** /**
* Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain text * Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain text
* {@code "error"}: * {@code "error"}:
@ -793,6 +870,98 @@ public interface LocatorAssertions {
* @since v1.20 * @since v1.20
*/ */
void isVisible(IsVisibleOptions options); void isVisible(IsVisibleOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(String expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(String expected, ContainsClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(List<String> expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(List<String> expected, ContainsClassOptions options);
/** /**
* Ensures the {@code Locator} points to an element that contains the given text. All nested elements will be considered * Ensures the {@code Locator} points to an element that contains the given text. All nested elements will be considered
* when computing the text content of the element. You can use regular expressions for the value as well. * when computing the text content of the element. You can use regular expressions for the value as well.
@ -1205,6 +1374,66 @@ public interface LocatorAssertions {
* @since v1.44 * @since v1.44
*/ */
void hasAccessibleDescription(Pattern description, HasAccessibleDescriptionOptions options); void hasAccessibleDescription(Pattern description, HasAccessibleDescriptionOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
default void hasAccessibleErrorMessage(String errorMessage) {
hasAccessibleErrorMessage(errorMessage, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMessageOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
default void hasAccessibleErrorMessage(Pattern errorMessage) {
hasAccessibleErrorMessage(errorMessage, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
void hasAccessibleErrorMessage(Pattern errorMessage, HasAccessibleErrorMessageOptions options);
/** /**
* Ensures the {@code Locator} points to an element with a given <a * Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. * href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
@ -1322,18 +1551,21 @@ public interface LocatorAssertions {
*/ */
void hasAttribute(String name, Pattern value, HasAttributeOptions options); void hasAttribute(String name, Pattern value, HasAttributeOptions options);
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1343,18 +1575,21 @@ public interface LocatorAssertions {
hasClass(expected, null); hasClass(expected, null);
} }
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1362,18 +1597,21 @@ public interface LocatorAssertions {
*/ */
void hasClass(String expected, HasClassOptions options); void hasClass(String expected, HasClassOptions options);
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1383,18 +1621,21 @@ public interface LocatorAssertions {
hasClass(expected, null); hasClass(expected, null);
} }
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1402,18 +1643,21 @@ public interface LocatorAssertions {
*/ */
void hasClass(Pattern expected, HasClassOptions options); void hasClass(Pattern expected, HasClassOptions options);
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1423,18 +1667,21 @@ public interface LocatorAssertions {
hasClass(expected, null); hasClass(expected, null);
} }
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1442,18 +1689,21 @@ public interface LocatorAssertions {
*/ */
void hasClass(String[] expected, HasClassOptions options); void hasClass(String[] expected, HasClassOptions options);
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -1463,18 +1713,21 @@ public interface LocatorAssertions {
hasClass(expected, null); hasClass(expected, null);
} }
/** /**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a * Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* relaxed regular expression. * the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* *
* <p> <strong>Usage</strong> * <p> <strong>Usage</strong>
* <pre>{@code * <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected")); * assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass("selected row"); * assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre> * }</pre>
* *
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted: * <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code * <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"}); * assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre> * }</pre>
* *
* @param expected Expected class or RegExp or a list of those. * @param expected Expected class or RegExp or a list of those.
@ -2097,7 +2350,7 @@ public interface LocatorAssertions {
* *
* <p> For example, given the following element: * <p> For example, given the following element:
* <pre>{@code * <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]); * page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") }); * assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre> * }</pre>
* *
@ -2115,7 +2368,7 @@ public interface LocatorAssertions {
* *
* <p> For example, given the following element: * <p> For example, given the following element:
* <pre>{@code * <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]); * page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") }); * assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre> * }</pre>
* *
@ -2131,7 +2384,7 @@ public interface LocatorAssertions {
* *
* <p> For example, given the following element: * <p> For example, given the following element:
* <pre>{@code * <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]); * page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") }); * assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre> * }</pre>
* *
@ -2149,7 +2402,7 @@ public interface LocatorAssertions {
* *
* <p> For example, given the following element: * <p> For example, given the following element:
* <pre>{@code * <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]); * page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") }); * assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre> * }</pre>
* *
@ -2157,5 +2410,39 @@ public interface LocatorAssertions {
* @since v1.23 * @since v1.23
*/ */
void hasValues(Pattern[] values, HasValuesOptions options); void hasValues(Pattern[] values, HasValuesOptions options);
/**
* Asserts that the target element matches the given <a
* href="https://playwright.dev/java/docs/aria-snapshots">accessibility snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page.locator("body")).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.49
*/
default void matchesAriaSnapshot(String expected) {
matchesAriaSnapshot(expected, null);
}
/**
* Asserts that the target element matches the given <a
* href="https://playwright.dev/java/docs/aria-snapshots">accessibility snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page.locator("body")).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.49
*/
void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions options);
} }

View File

@ -22,14 +22,14 @@ import java.util.regex.Pattern;
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} * The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page}
* state in the tests. * state in the tests.
* <pre>{@code * <pre>{@code
* ... * // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
* *
* public class TestPage { * public class TestPage {
* ... * // ...
* @Test * @Test
* void navigatesToLoginPage() { * void navigatesToLoginPage() {
* ... * // ...
* page.getByText("Sign in").click(); * page.getByText("Sign in").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login")); * assertThat(page).hasURL(Pattern.compile(".*\/login"));
* } * }
@ -54,7 +54,7 @@ public interface PageAssertions {
class HasURLOptions { class HasURLOptions {
/** /**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified. * expression parameter if specified. A provided predicate ignores this flag.
*/ */
public Boolean ignoreCase; public Boolean ignoreCase;
/** /**
@ -64,7 +64,7 @@ public interface PageAssertions {
/** /**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified. * expression parameter if specified. A provided predicate ignores this flag.
*/ */
public HasURLOptions setIgnoreCase(boolean ignoreCase) { public HasURLOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase; this.ignoreCase = ignoreCase;

View File

@ -30,14 +30,13 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
* *
* <p> Consider the following example: * <p> Consider the following example:
* <pre>{@code * <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; * import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
* *
* public class TestExample { * public class TestExample {
* ... * // ...
* @Test * @Test
* void statusBecomesSubmitted() { * void statusBecomesSubmitted() {
* ... * // ...
* page.locator("#submit-button").click(); * page.locator("#submit-button").click();
* assertThat(page.locator(".status")).hasText("Submitted"); * assertThat(page.locator(".status")).hasText("Submitted");
* } * }

View File

@ -39,6 +39,8 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing; private final TracingImpl tracing;
private String disposeReason; private String disposeReason;
protected TimeoutSettings timeoutSettings = new TimeoutSettings();
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString()); this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
@ -51,21 +53,17 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@Override @Override
public void dispose(DisposeOptions options) { public void dispose(DisposeOptions options) {
withLogging("APIRequestContext.dispose", () -> disposeImpl(options));
}
private void disposeImpl(DisposeOptions options) {
if (options == null) { if (options == null) {
options = new DisposeOptions(); options = new DisposeOptions();
} }
disposeReason = options.reason; disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params); sendMessage("dispose", params, NO_TIMEOUT);
} }
@Override @Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) { public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options)); return fetchImpl(urlOrRequest, (RequestOptionsImpl) options);
} }
@Override @Override
@ -93,6 +91,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options == null) { if (options == null) {
options = new RequestOptionsImpl(); options = new RequestOptionsImpl();
} }
options.timeout = timeoutSettings.timeout(options.timeout);
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("url", url); params.addProperty("url", url);
if (options.params != null) { if (options.params != null) {
@ -132,9 +131,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options.multipart != null) { if (options.multipart != null) {
params.add("multipartData", serializeMultipartData(options.multipart.fields)); params.add("multipartData", serializeMultipartData(options.multipart.fields));
} }
if (options.timeout != null) {
params.addProperty("timeout", options.timeout);
}
if (options.failOnStatusCode != null) { if (options.failOnStatusCode != null) {
params.addProperty("failOnStatusCode", options.failOnStatusCode); params.addProperty("failOnStatusCode", options.failOnStatusCode);
} }
@ -153,7 +149,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
} }
params.addProperty("maxRetries", options.maxRetries); params.addProperty("maxRetries", options.maxRetries);
} }
JsonObject json = sendMessage("fetch", params).getAsJsonObject(); JsonObject json = sendMessage("fetch", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response")); return new APIResponseImpl(this, json.getAsJsonObject("response"));
} }
@ -219,14 +215,12 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@Override @Override
public String storageState(StorageStateOptions options) { public String storageState(StorageStateOptions options) {
return withLogging("APIRequestContext.storageState", () -> {
JsonElement json = sendMessage("storageState"); JsonElement json = sendMessage("storageState");
String storageState = json.toString(); String storageState = json.toString();
if (options != null && options.path != null) { if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path); Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
} }
return storageState; return storageState;
});
} }
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) { private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {

View File

@ -17,7 +17,6 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest; import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.PlaywrightException;
@ -26,12 +25,11 @@ import com.microsoft.playwright.options.ClientCertificate;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Base64;
import java.util.List; import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol; import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static java.nio.file.Files.readAllBytes;
class APIRequestImpl implements APIRequest { class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright; private final PlaywrightImpl playwright;
@ -42,10 +40,6 @@ class APIRequestImpl implements APIRequest {
@Override @Override
public APIRequestContextImpl newContext(NewContextOptions options) { public APIRequestContextImpl newContext(NewContextOptions options) {
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
}
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) { if (options == null) {
options = new NewContextOptions(); options = new NewContextOptions();
} else { } else {
@ -67,13 +61,17 @@ class APIRequestImpl implements APIRequest {
} }
List<ClientCertificate> clientCertificateList = options.clientCertificates; List<ClientCertificate> clientCertificateList = options.clientCertificates;
options.clientCertificates = null; options.clientCertificates = null;
Double timeout = options.timeout;
// Timeout is handled on the client.
options.timeout = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) { if (storageState != null) {
params.add("storageState", storageState); params.add("storageState", storageState);
} }
addToProtocol(params, clientCertificateList); addToProtocol(params, clientCertificateList);
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject(); JsonObject result = playwright.sendMessage("newRequest", params, NO_TIMEOUT).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString()); APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
context.timeoutSettings.setDefaultTimeout(timeout);
return context; return context;
} }
} }

View File

@ -17,7 +17,6 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.APIResponse; import com.microsoft.playwright.APIResponse;
@ -29,6 +28,7 @@ import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError; import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@ -46,11 +46,10 @@ class APIResponseImpl implements APIResponse {
@Override @Override
public byte[] body() { public byte[] body() {
return context.withLogging("APIResponse.body", () -> {
try { try {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid()); params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject(); JsonObject json = context.sendMessage("fetchResponseBody", params, NO_TIMEOUT).getAsJsonObject();
if (!json.has("binary")) { if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed"); throw new PlaywrightException("Response has been disposed");
} }
@ -61,16 +60,13 @@ class APIResponseImpl implements APIResponse {
} }
throw e; throw e;
} }
});
} }
@Override @Override
public void dispose() { public void dispose() {
context.withLogging("APIResponse.dispose", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid()); params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params); context.sendMessage("disposeAPIResponse", params, NO_TIMEOUT);
});
} }
@Override @Override
@ -116,7 +112,7 @@ class APIResponseImpl implements APIResponse {
List<String> fetchLog() { List<String> fetchLog() {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid()); params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject(); JsonObject json = context.sendMessage("fetchLog", params, NO_TIMEOUT).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray(); JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType()); return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
} }

View File

@ -86,6 +86,6 @@ class ArtifactImpl extends ChannelOwner {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("path", path.toString()); params.addProperty("path", path.toString());
sendMessage("saveAs", params); sendMessage("saveAs", params, NO_TIMEOUT);
} }
} }

View File

@ -16,7 +16,6 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError; import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper; import org.opentest4j.ValueWrapper;
@ -29,39 +28,37 @@ import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags; import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
class AssertionsBase { abstract class AssertionsBase {
final LocatorImpl actualLocator;
final boolean isNot; final boolean isNot;
AssertionsBase(LocatorImpl actual, boolean isNot) { AssertionsBase(boolean isNot) {
this.actualLocator = actual;
this.isNot = isNot; this.isNot = isNot;
} }
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) { void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options, String title) {
expectImpl(expression, asList(textValue), expected, message, options); expectImpl(expression, asList(textValue), expected, message, options, title);
} }
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) { void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options, String title) {
if (options == null) { if (options == null) {
options = new FrameExpectOptions(); options = new FrameExpectOptions();
} }
options.expectedText = expectedText; options.expectedText = expectedText;
options.isNot = isNot; expectImpl(expression, options, expected, message, title);
expectImpl(expression, options, expected, message);
} }
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) { void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message, String title) {
if (expectOptions.timeout == null) { if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout; expectOptions.timeout = AssertionsTimeout.defaultTimeout;
} }
if (expectOptions.isNot) { expectOptions.isNot = isNot;
if (isNot) {
message = message.replace("expected to", "expected not to"); message = message.replace("expected to", "expected not to");
} }
FrameExpectResult result = actualLocator.expect(expression, expectOptions); FrameExpectResult result = doExpect(expression, expectOptions, title);
if (result.matches == isNot) { if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received); Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = String.join("\n", result.log); String log = (result.log == null) ? "" : String.join("\n", result.log);
if (!log.isEmpty()) { if (!log.isEmpty()) {
log = "\nCall log:\n" + log; log = "\nCall log:\n" + log;
} }
@ -75,7 +72,9 @@ class AssertionsBase {
} }
} }
private static ValueWrapper formatValue(Object value) { abstract FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title);
protected static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) { if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value); return ValueWrapper.create(value);
} }

View File

@ -78,11 +78,11 @@ class BindingCall extends ChannelOwner {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("result", gson().toJsonTree(serializeArgument(result))); params.add("result", gson().toJsonTree(serializeArgument(result)));
sendMessage("resolve", params); sendMessage("resolve", params, NO_TIMEOUT);
} catch (RuntimeException exception) { } catch (RuntimeException exception) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("error", gson().toJsonTree(serializeError(exception))); params.add("error", gson().toJsonTree(serializeError(exception)));
sendMessage("reject", params); sendMessage("reject", params, NO_TIMEOUT);
} }
} }
} }

View File

@ -42,7 +42,7 @@ import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
class BrowserContextImpl extends ChannelOwner implements BrowserContext { class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser; protected BrowserImpl browser;
private final TracingImpl tracing; private final TracingImpl tracing;
private final APIRequestContextImpl request; private final APIRequestContextImpl request;
private final ClockImpl clock; private final ClockImpl clock;
@ -50,7 +50,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
final List<PageImpl> backgroundPages = new ArrayList<>(); final List<PageImpl> backgroundPages = new ArrayList<>();
final Router routes = new Router(); final Router routes = new Router();
private boolean closeWasCalled; final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closingOrClosed;
private final WaitableEvent<EventType, ?> closePromise; private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>(); final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage; PageImpl ownerPage;
@ -68,8 +69,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} }
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this); private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings(); final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
final Map<String, HarRecorder> harRecorders = new HashMap<>(); final Map<String, HarRecorder> harRecorders = new HashMap<>();
static class HarRecorder { static class HarRecorder {
@ -97,30 +96,31 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
} else {
browser = null;
}
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString()); tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString()); request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this); clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE); closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
} }
void setRecordHar(Path path, HarContentPolicy policy) { Path videosDir() {
if (path != null) { JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
harRecorders.put("", new HarRecorder(path, policy)); if (recordVideo == null) {
return null;
} }
return Paths.get(recordVideo.get("dir").getAsString());
} }
void setBaseUrl(String spec) { URL baseUrl() {
JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
if (url != null) {
try { try {
this.baseUrl = new URL(spec); return new URL(url.getAsString());
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
this.baseUrl = null;
} }
} }
return null;
}
String effectiveCloseReason() { String effectiveCloseReason() {
if (closeReason != null) { if (closeReason != null) {
@ -261,7 +261,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Page page) { public CDPSession newCDPSession(Page page) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("page", ((PageImpl) page).toProtocolRef()); params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject(); JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString()); return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
} }
@ -269,23 +269,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Frame frame) { public CDPSession newCDPSession(Frame frame) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("frame", ((FrameImpl) frame).toProtocolRef()); params.add("frame", ((FrameImpl) frame).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject(); JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString()); return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
} }
@Override @Override
public void close(CloseOptions options) { public void close(CloseOptions options) {
withLogging("BrowserContext.close", () -> closeImpl(options)); if (!closingOrClosed) {
} closingOrClosed = true;
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) { if (options == null) {
options = new CloseOptions(); options = new CloseOptions();
} }
@ -294,7 +285,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) { for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey()); params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params).getAsJsonObject(); JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString()); ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// Server side will compress artifact if content is attach or if file is .zip. // Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue(); HarRecorder harParams = entry.getValue();
@ -306,42 +297,46 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject unzipParams = new JsonObject(); JsonObject unzipParams = new JsonObject();
unzipParams.addProperty("zipFile", tmpPath); unzipParams.addProperty("zipFile", tmpPath);
unzipParams.addProperty("harFile", harParams.path.toString()); unzipParams.addProperty("harFile", harParams.path.toString());
connection.localUtils.sendMessage("harUnzip", unzipParams); connection.localUtils.sendMessage("harUnzip", unzipParams, NO_TIMEOUT);
} else { } else {
artifact.saveAs(harParams.path); artifact.saveAs(harParams.path);
} }
artifact.delete(); artifact.delete();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params); sendMessage("close", params, NO_TIMEOUT);
} }
runUntil(() -> {}, closePromise); runUntil(() -> {}, closePromise);
} }
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
@Override @Override
public void addCookies(List<Cookie> cookies) { public void addCookies(List<Cookie> cookies) {
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies)); params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params); sendMessage("addCookies", params, NO_TIMEOUT);
});
} }
@Override @Override
public void addInitScript(String script) { public void addInitScript(String script) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script)); JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params, NO_TIMEOUT);
} }
@Override @Override
public void addInitScript(Path path) { public void addInitScript(Path path) {
withLogging("BrowserContext.addInitScript", () -> {
try { try {
byte[] bytes = readAllBytes(path); byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8)); addInitScript(new String(bytes, UTF_8));
} catch (IOException e) { } catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e); throw new PlaywrightException("Failed to read script from file", e);
} }
});
} }
@Override @Override
@ -349,12 +344,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return new ArrayList<>(backgroundPages); return new ArrayList<>(backgroundPages);
} }
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params);
}
@Override @Override
public BrowserImpl browser() { public BrowserImpl browser() {
return browser; return browser;
@ -362,10 +351,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override @Override
public void clearCookies(ClearCookiesOptions options) { public void clearCookies(ClearCookiesOptions options) {
withLogging("BrowserContext.clearCookies", () -> clearCookiesImpl(options));
}
private void clearCookiesImpl(ClearCookiesOptions options) {
if (options == null) { if (options == null) {
options = new ClearCookiesOptions(); options = new ClearCookiesOptions();
} }
@ -373,7 +358,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
setStringOrRegex(params, "name", options.name); setStringOrRegex(params, "name", options.name);
setStringOrRegex(params, "domain", options.domain); setStringOrRegex(params, "domain", options.domain);
setStringOrRegex(params, "path", options.path); setStringOrRegex(params, "path", options.path);
sendMessage("clearCookies", params); sendMessage("clearCookies", params, NO_TIMEOUT);
} }
private static void setStringOrRegex(JsonObject params, String name, Object value) { private static void setStringOrRegex(JsonObject params, String name, Object value) {
@ -388,28 +373,24 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override @Override
public void clearPermissions() { public void clearPermissions() {
withLogging("BrowserContext.clearPermissions", () -> sendMessage("clearPermissions")); sendMessage("clearPermissions");
} }
@Override @Override
public List<Cookie> cookies(List<String> urls) { public List<Cookie> cookies(List<String> urls) {
return withLogging("BrowserContext.cookies", () -> cookiesImpl(urls));
}
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
if (urls == null) { if (urls == null) {
urls = new ArrayList<>(); urls = new ArrayList<>();
} }
params.add("urls", gson().toJsonTree(urls)); params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject(); JsonObject json = sendMessage("cookies", params, NO_TIMEOUT).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class); Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return asList(cookies); return asList(cookies);
} }
@Override @Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) { public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options)); exposeBindingImpl(name, playwrightBinding, options);
} }
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) { private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
@ -428,21 +409,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (options != null && options.handle != null && options.handle) { if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true); params.addProperty("needsHandle", true);
} }
sendMessage("exposeBinding", params); sendMessage("exposeBinding", params, NO_TIMEOUT);
} }
@Override @Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) { public void exposeFunction(String name, FunctionCallback playwrightFunction) {
withLogging("BrowserContext.exposeFunction", exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
} }
@Override @Override
public void grantPermissions(List<String> permissions, GrantPermissionsOptions options) { public void grantPermissions(List<String> permissions, GrantPermissionsOptions options) {
withLogging("BrowserContext.grantPermissions", () -> grantPermissionsImpl(permissions, options));
}
private void grantPermissionsImpl(List<String> permissions, GrantPermissionsOptions options) {
if (options == null) { if (options == null) {
options = new GrantPermissionsOptions(); options = new GrantPermissionsOptions();
} }
@ -451,15 +427,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions)); params.add("permissions", gson().toJsonTree(permissions));
sendMessage("grantPermissions", params); sendMessage("grantPermissions", params, NO_TIMEOUT);
} }
@Override @Override
public PageImpl newPage() { public PageImpl newPage() {
return withLogging("BrowserContext.newPage", () -> newPageImpl());
}
private PageImpl newPageImpl() {
if (ownerPage != null) { if (ownerPage != null) {
throw new PlaywrightException("Please use browser.newContext()"); throw new PlaywrightException("Please use browser.newContext()");
} }
@ -479,7 +451,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override @Override
public void route(String url, Consumer<Route> handler, RouteOptions options) { public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(baseUrl, url), handler, options); route(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, false), handler, options);
} }
@Override @Override
@ -498,73 +470,76 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new RouteFromHAROptions(); options = new RouteFromHAROptions();
} }
if (options.update != null && options.update) { if (options.update != null && options.update) {
recordIntoHar(null, har, options); recordIntoHar(null, har, options, null);
return; return;
} }
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url); UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound); HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose()); onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null); route(matcher, route -> harRouter.handle(route), null);
} }
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) { private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times); routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns(); updateInterceptionPatterns();
});
} }
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) { @Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
}
@Override
public void routeWebSocket(Pattern pattern, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(pattern), handler);
}
@Override
public void routeWebSocket(Predicate<String> predicate, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(predicate), handler);
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
if (contentPolicy == null) {
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);
}
if (contentPolicy == null) {
contentPolicy = HarContentPolicy.ATTACH;
}
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
if (page != null) { if (page != null) {
params.add("page", page.toProtocolRef()); params.add("page", page.toProtocolRef());
} }
JsonObject jsonOptions = new JsonObject(); JsonObject recordHarArgs = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString()); recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
jsonOptions.addProperty("content", options.updateContent == null ? recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
HarContentPolicy.ATTACH.name().toLowerCase() : recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
options.updateContent.name().toLowerCase()); addHarUrlFilter(recordHarArgs, options.url);
jsonOptions.addProperty("mode", options.updateMode == null ?
HarMode.MINIMAL.name().toLowerCase() : params.add("options", recordHarArgs);
options.updateMode.name().toLowerCase()); JsonObject json = sendMessage("harStart", params, NO_TIMEOUT).getAsJsonObject();
addHarUrlFilter(jsonOptions, options.url);
params.add("options", jsonOptions);
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
String harId = json.get("harId").getAsString(); String harId = json.get("harId").getAsString();
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH)); harRecorders.put(harId, new HarRecorder(har, contentPolicy));
} }
@Override @Override
public void setDefaultNavigationTimeout(double timeout) { public void setDefaultNavigationTimeout(double timeout) {
setDefaultNavigationTimeoutImpl(timeout);
}
void setDefaultNavigationTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout); timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
} }
@Override @Override
public void setDefaultTimeout(double timeout) { public void setDefaultTimeout(double timeout) {
setDefaultTimeoutImpl(timeout);
}
void setDefaultTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout); timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
} }
@Override @Override
public void setExtraHTTPHeaders(Map<String, String> headers) { public void setExtraHTTPHeaders(Map<String, String> headers) {
withLogging("BrowserContext.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray(); JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) { for (Map.Entry<String, String> e : headers.entrySet()) {
@ -574,40 +549,39 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
jsonHeaders.add(header); jsonHeaders.add(header);
} }
params.add("headers", jsonHeaders); params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params); sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
});
} }
@Override @Override
public void setGeolocation(Geolocation geolocation) { public void setGeolocation(Geolocation geolocation) {
withLogging("BrowserContext.setGeolocation", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
if (geolocation != null) { if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation)); params.add("geolocation", gson().toJsonTree(geolocation));
} }
sendMessage("setGeolocation", params); sendMessage("setGeolocation", params, NO_TIMEOUT);
});
} }
@Override @Override
public void setOffline(boolean offline) { public void setOffline(boolean offline) {
withLogging("BrowserContext.setOffline", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("offline", offline); params.addProperty("offline", offline);
sendMessage("setOffline", params); sendMessage("setOffline", params, NO_TIMEOUT);
});
} }
@Override @Override
public String storageState(StorageStateOptions options) { public String storageState(StorageStateOptions options) {
return withLogging("BrowserContext.storageState", () -> { if (options == null) {
JsonElement json = sendMessage("storageState"); options = new StorageStateOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonElement json = sendMessage("storageState", params, NO_TIMEOUT);
String storageState = json.toString(); String storageState = json.toString();
if (options != null && options.path != null) { if (options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path); Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
} }
return storageState; return storageState;
});
} }
@Override @Override
@ -617,15 +591,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override @Override
public void unrouteAll() { public void unrouteAll() {
withLogging("BrowserContext.unrouteAll", () -> {
routes.removeAll(); routes.removeAll();
updateInterceptionPatterns(); updateInterceptionPatterns();
});
} }
@Override @Override
public void unroute(String url, Consumer<Route> handler) { public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(this.baseUrl, url), handler); unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
} }
@Override @Override
@ -671,14 +643,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} }
private void unroute(UrlMatcher matcher, Consumer<Route> handler) { private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler); routes.remove(matcher, handler);
updateInterceptionPatterns(); updateInterceptionPatterns();
});
} }
private void updateInterceptionPatterns() { private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns()); sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
} }
void handleRoute(RouteImpl route) { void handleRoute(RouteImpl route) {
@ -691,6 +665,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} }
} }
void handleWebSocketRoute(WebSocketRouteImpl route) {
if (!webSocketRoutes.handle(route)) {
route.connectToServer();
}
}
WaitableResult<JsonElement> pause() { WaitableResult<JsonElement> pause() {
return sendMessageAsync("pause", new JsonObject()); return sendMessageAsync("pause", new JsonObject());
} }
@ -730,6 +710,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = this; route.browserContext = this;
handleRoute(route); handleRoute(route);
} else if ("webSocketRoute".equals(event)) {
WebSocketRouteImpl route = connection.getExistingObject(params.getAsJsonObject("webSocketRoute").get("guid").getAsString());
handleWebSocketRoute(route);
} else if ("page".equals(event)) { } else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString()); PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page); pages.add(page);
@ -822,8 +805,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} }
void didClose() { void didClose() {
closingOrClosed = true;
if (browser != null) { if (browser != null) {
browser.contexts.remove(this); browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
} }
listeners.notify(EventType.CLOSE, this); listeners.notify(EventType.CLOSE, this);
} }
@ -832,7 +817,49 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("name", name); params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs); params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject(); JsonObject json = sendMessage("createTempFile", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
} }
protected void initializeHarFromOptions(Browser.NewContextOptions options) {
if (options.recordHarPath == null) {
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");
}
return;
}
HarContentPolicy contentPolicy = options.recordHarContent;
if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();
if (options.recordHarUrlFilter instanceof String) {
routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
}
if (options.recordHarMode != null) {
routeFromHAROptions.updateMode = options.recordHarMode;
} else {
routeFromHAROptions.updateMode = HarMode.FULL;
}
routeFromHAROptions.url = options.recordHarUrlFilter;
recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
}
} }

View File

@ -20,7 +20,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.*; import com.microsoft.playwright.*;
import com.microsoft.playwright.options.HarContentPolicy;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -29,10 +28,8 @@ import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.function.Consumer; 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.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.*; import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserImpl extends ChannelOwner implements Browser { class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>(); final Set<BrowserContextImpl> contexts = new HashSet<>();
@ -69,10 +66,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override @Override
public void close(CloseOptions options) { public void close(CloseOptions options) {
withLogging("Browser.close", () -> closeImpl(options));
}
private void closeImpl(CloseOptions options) {
if (options == null) { if (options == null) {
options = new CloseOptions(); options = new CloseOptions();
} }
@ -117,16 +110,20 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override @Override
public BrowserContextImpl newContext(NewContextOptions options) { public BrowserContextImpl newContext(NewContextOptions options) {
return withLogging("Browser.newContext", () -> newContextImpl(options));
}
private BrowserContextImpl newContextImpl(NewContextOptions options) {
if (options == null) { if (options == null) {
options = new NewContextOptions(); options = new NewContextOptions();
} else { } else {
// Make a copy so that we can nullify some fields below. // Make a copy so that we can nullify some fields below.
options = convertType(options, NewContextOptions.class); options = convertType(options, NewContextOptions.class);
} }
NewContextOptions harOptions = Utils.clone(options);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
if (options.storageStatePath != null) { if (options.storageStatePath != null) {
try { try {
byte[] bytes = Files.readAllBytes(options.storageStatePath); byte[] bytes = Files.readAllBytes(options.storageStatePath);
@ -141,51 +138,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
storageState = new Gson().fromJson(options.storageState, JsonObject.class); storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null; 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(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) { if (storageState != null) {
params.add("storageState", storageState); params.add("storageState", storageState);
} }
if (recordHar != null) {
params.add("recordHar", recordHar);
}
if (options.recordVideoDir != null) { if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject(); JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString()); recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@ -213,31 +169,21 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.acceptDownloads != null) { if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny"); params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
} }
JsonElement result = sendMessage("newContext", params); params.add("selectorEngines", gson().toJsonTree(browserType.playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", browserType.playwright.selectors.testIdAttributeName);
JsonElement result = sendMessage("newContext", params, NO_TIMEOUT);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString()); BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir; context.initializeHarFromOptions(harOptions);
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
if (launchOptions != null) {
context.tracing().setTracesDir(launchOptions.tracesDir);
}
contexts.add(context);
return context; return context;
} }
@Override @Override
public Page newPage(NewPageOptions options) { public Page newPage(NewPageOptions options) {
return withLogging("Browser.newPage", () -> newPageImpl(options)); return withTitle("Create Page", () -> newPageImpl(options));
} }
@Override @Override
public void startTracing(Page page, StartTracingOptions options) { public void startTracing(Page page, StartTracingOptions options) {
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
}
private void startTracingImpl(Page page, StartTracingOptions options) {
if (options == null) { if (options == null) {
options = new StartTracingOptions(); options = new StartTracingOptions();
} }
@ -246,15 +192,11 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (page != null) { if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef()); params.add("page", ((PageImpl) page).toProtocolRef());
} }
sendMessage("startTracing", params); sendMessage("startTracing", params, NO_TIMEOUT);
} }
@Override @Override
public byte[] stopTracing() { public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject(); JsonObject json = sendMessage("stopTracing").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString()); ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
byte[] data = artifact.readAllBytes(); byte[] data = artifact.readAllBytes();
@ -295,18 +237,46 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override @Override
void handleEvent(String event, JsonObject parameters) { void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) { switch (event) {
case "context":
didCreateContext(connection.getExistingObject(parameters.getAsJsonObject("context").get("guid").getAsString()));
break;
case "close":
didClose(); didClose();
break;
} }
} }
@Override @Override
public CDPSession newBrowserCDPSession() { public CDPSession newBrowserCDPSession() {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params).getAsJsonObject(); JsonObject result = sendMessage("newBrowserCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString()); return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
} }
protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir){
// Note: when using connect(), `browserType` is different from `this.parent`.
// This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
this.browserType = browserType;
this.tracePath = tracesDir;
for (BrowserContextImpl context : contexts) {
context.tracing().setTracesDir(tracesDir);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didCreateContext(BrowserContextImpl context) {
context.browser = this;
contexts.add(context);
// Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
// and will be configured later in `ConnectToBrowserType`.
if (browserType != null) {
context.tracing().setTracesDir(tracePath);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didClose() { private void didClose() {
isConnected = false; isConnected = false;
listeners.notify(EventType.DISCONNECTED, this); listeners.notify(EventType.DISCONNECTED, this);

View File

@ -22,33 +22,30 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser; import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType; import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HarContentPolicy;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer; 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.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol; import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static com.microsoft.playwright.impl.Utils.convertType; import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType { class BrowserTypeImpl extends ChannelOwner implements BrowserType {
protected PlaywrightImpl playwright;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
} }
@Override @Override
public BrowserImpl launch(LaunchOptions options) { public BrowserImpl launch(LaunchOptions options) {
return withLogging("BrowserType.launch", () -> launchImpl(options));
}
private BrowserImpl launchImpl(LaunchOptions options) {
if (options == null) { if (options == null) {
options = new LaunchOptions(); options = new LaunchOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params); JsonElement result = sendMessage("launch", params, TimeoutSettings.launchTimeout(options.timeout));
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString()); BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this; browser.browserType = this;
browser.launchOptions = options; browser.launchOptions = options;
@ -57,10 +54,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override @Override
public Browser connect(String wsEndpoint, ConnectOptions options) { public Browser connect(String wsEndpoint, ConnectOptions options) {
return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
}
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
if (options == null) { if (options == null) {
options = new ConnectOptions(); options = new ConnectOptions();
} }
@ -83,7 +76,12 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
headers.addProperty("x-playwright-browser", name()); headers.addProperty("x-playwright-browser", name());
} }
JsonObject json = connection.localUtils().sendMessage("connect", params).getAsJsonObject(); Double timeout = options.timeout;
if (timeout == null) {
timeout = 0.0;
}
JsonObject json = connection.localUtils().sendMessage("connect", params, timeout).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString()); JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils); Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
PlaywrightImpl playwright = connection.initializePlaywright(); PlaywrightImpl playwright = connection.initializePlaywright();
@ -95,14 +93,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
} }
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?"); throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
} }
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright")); playwright.selectors = this.playwright.selectors;
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString()); BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isConnectedOverWebSocket = true; browser.isConnectedOverWebSocket = true;
browser.browserType = this; browser.connectToBrowserType(this, null);
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed(); Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener); pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> { browser.onDisconnected(b -> {
playwright.unregisterSelectors();
pipe.offClose(connectionCloseListener); pipe.offClose(connectionCloseListener);
try { try {
connection.close(); connection.close();
@ -118,25 +115,16 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (!"chromium".equals(name())) { if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium."); throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
} }
return withLogging("BrowserType.connectOverCDP", () -> connectOverCDPImpl(endpointURL, options));
}
private Browser connectOverCDPImpl(String endpointURL, ConnectOverCDPOptions options) {
if (options == null) { if (options == null) {
options = new ConnectOverCDPOptions(); options = new ConnectOverCDPOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("endpointURL", endpointURL); params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject(); JsonObject json = sendMessage("connectOverCDP", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString()); BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this; browser.connectToBrowserType(this, null);
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
return browser; return browser;
} }
@ -146,60 +134,26 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override @Override
public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) { public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
return withLogging("BrowserType.launchPersistentContext",
() -> launchPersistentContextImpl(userDataDir, options));
}
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
if (options == null) { if (options == null) {
options = new LaunchPersistentContextOptions(); options = new LaunchPersistentContextOptions();
} else { } else {
// Make a copy so that we can nullify some fields below. // Make a copy so that we can nullify some fields below.
options = convertType(options, LaunchPersistentContextOptions.class); options = convertType(options, LaunchPersistentContextOptions.class);
} }
JsonObject recordHar = null;
Path recordHarPath = options.recordHarPath; Browser.NewContextOptions harOptions = convertType(options, Browser.NewContextOptions.class);
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.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = 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(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("userDataDir", userDataDir.toString()); if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
if (recordHar != null) { Path cwd = Paths.get("").toAbsolutePath();
params.add("recordHar", recordHar); userDataDir = cwd.resolve(userDataDir);
} }
params.addProperty("userDataDir", userDataDir.toString());
if (options.recordVideoDir != null) { if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject(); JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString()); recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@ -227,13 +181,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.acceptDownloads != null) { if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny"); params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
} }
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject(); params.add("selectorEngines", gson().toJsonTree(playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", playwright.selectors.testIdAttributeName);
JsonObject json = sendMessage("launchPersistentContext", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, options.tracesDir);
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString()); BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir; context.initializeHarFromOptions(harOptions);
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.tracing().setTracesDir(options.tracesDir); context.tracing().setTracesDir(options.tracesDir);
return context; return context;
} }

View File

@ -36,6 +36,8 @@ class ChannelOwner extends LoggingSupport {
final JsonObject initializer; final JsonObject initializer;
private boolean wasCollected; private boolean wasCollected;
static Double NO_TIMEOUT = null;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) { protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer); this(parent.connection, parent, type, guid, initializer);
} }
@ -82,14 +84,25 @@ class ChannelOwner extends LoggingSupport {
return new WaitForEventLogger<>(this, apiName, code).get(); return new WaitForEventLogger<>(this, apiName, code).get();
} }
@Override
<T> T withLogging(String apiName, Supplier<T> code) { void withTitle(String title, Runnable code) {
String previousApiName = connection.setApiName(apiName); withTitle(title, () -> {
try { code.run();
return super.withLogging(apiName, code); return null;
} finally { });
connection.setApiName(previousApiName);
} }
<T> T withTitle(String title, Supplier<T> code) {
String previousTitle = connection.setTitle(title);
try {
return code.get();
} finally {
connection.setTitle(previousTitle);
}
}
WaitableResult<JsonElement> sendMessageAsync(String method) {
return sendMessageAsync(method, new JsonObject());
} }
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) { WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
@ -98,11 +111,16 @@ class ChannelOwner extends LoggingSupport {
} }
JsonElement sendMessage(String method) { JsonElement sendMessage(String method) {
return sendMessage(method, new JsonObject()); return sendMessage(method, new JsonObject(), NO_TIMEOUT);
} }
JsonElement sendMessage(String method, JsonObject params) { JsonElement sendMessage(String method, JsonObject params, Double timeout) {
checkNotCollected(); checkNotCollected();
if (timeout != null) {
params.addProperty("timeout", timeout);
} else if (params.has("timeout")) {
throw new PlaywrightException("Internal error: timeout must be passed explicitly.");
}
return connection.sendMessage(guid, method, params); return connection.sendMessage(guid, method, params);
} }

View File

@ -5,6 +5,8 @@ import com.microsoft.playwright.Clock;
import java.util.Date; import java.util.Date;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class ClockImpl implements Clock { class ClockImpl implements Clock {
private final ChannelOwner browserContext; private final ChannelOwner browserContext;
@ -12,18 +14,23 @@ class ClockImpl implements Clock {
this.browserContext = browserContext; this.browserContext = browserContext;
} }
private void sendMessageWithLogging(String method, JsonObject params) {
String capitalizedMethod = method.substring(0, 1).toUpperCase() + method.substring(1);
browserContext.sendMessage("clock" + capitalizedMethod, params, NO_TIMEOUT);
}
@Override @Override
public void fastForward(long ticks) { public void fastForward(long ticks) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks); params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockFastForward", params); sendMessageWithLogging("fastForward", params);
} }
@Override @Override
public void fastForward(String ticks) { public void fastForward(String ticks) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks); params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockFastForward", params); sendMessageWithLogging("fastForward", params);
} }
@Override @Override
@ -32,89 +39,89 @@ class ClockImpl implements Clock {
if (options != null) { if (options != null) {
parseTime(options.time, params); parseTime(options.time, params);
} }
browserContext.sendMessage("clockInstall", params); sendMessageWithLogging("install", params);
} }
@Override @Override
public void runFor(long ticks) { public void runFor(long ticks) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks); params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockRunFor", params); sendMessageWithLogging("runFor", params);
} }
@Override @Override
public void runFor(String ticks) { public void runFor(String ticks) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks); params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockRunFor", params); sendMessageWithLogging("runFor", params);
} }
@Override @Override
public void pauseAt(long time) { public void pauseAt(long time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time); params.addProperty("timeNumber", time);
browserContext.sendMessage("clockPauseAt", params); sendMessageWithLogging("pauseAt", params);
} }
@Override @Override
public void pauseAt(String time) { public void pauseAt(String time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeString", time); params.addProperty("timeString", time);
browserContext.sendMessage("clockPauseAt", params); sendMessageWithLogging("pauseAt", params);
} }
@Override @Override
public void pauseAt(Date time) { public void pauseAt(Date time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime()); params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockPauseAt", params); sendMessageWithLogging("pauseAt", params);
} }
@Override @Override
public void resume() { public void resume() {
browserContext.sendMessage("clockResume"); sendMessageWithLogging("resume", new JsonObject());
} }
@Override @Override
public void setFixedTime(long time) { public void setFixedTime(long time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time); params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetFixedTime", params); sendMessageWithLogging("setFixedTime", params);
} }
@Override @Override
public void setFixedTime(String time) { public void setFixedTime(String time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeString", time); params.addProperty("timeString", time);
browserContext.sendMessage("clockSetFixedTime", params); sendMessageWithLogging("setFixedTime", params);
} }
@Override @Override
public void setFixedTime(Date time) { public void setFixedTime(Date time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime()); params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetFixedTime", params); sendMessageWithLogging("setFixedTime", params);
} }
@Override @Override
public void setSystemTime(long time) { public void setSystemTime(long time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time); params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetSystemTime", params); sendMessageWithLogging("setSystemTime", params);
} }
@Override @Override
public void setSystemTime(String time) { public void setSystemTime(String time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeString", time); params.addProperty("timeString", time);
browserContext.sendMessage("clockSetSystemTime", params); sendMessageWithLogging("setSystemTime", params);
} }
@Override @Override
public void setSystemTime(Date time) { public void setSystemTime(Date time) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime()); params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetSystemTime", params); sendMessageWithLogging("setSystemTime", params);
} }
private static void parseTime(Object time, JsonObject params) { private static void parseTime(Object time, JsonObject params) {

View File

@ -19,7 +19,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError; import com.microsoft.playwright.TimeoutError;
@ -64,7 +63,8 @@ public class Connection {
private int lastId = 0; private int lastId = 0;
private final StackTraceCollector stackTraceCollector; private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>(); private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName; private String title;
private boolean titleReported = false;
private static final boolean isLogging; private static final boolean isLogging;
static { static {
String debug = System.getenv("DEBUG"); String debug = System.getenv("DEBUG");
@ -83,7 +83,7 @@ public class Connection {
PlaywrightImpl initialize() { PlaywrightImpl initialize() {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java"); params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject()); JsonElement result = sendMessage("initialize", params.getAsJsonObject(), NO_TIMEOUT);
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString()); return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
} }
} }
@ -116,9 +116,10 @@ public class Connection {
} }
} }
String setApiName(String name) { String setTitle(String newTitle) {
String previous = apiName; String previous = title;
apiName = name; titleReported = false;
title = newTitle;
return previous; return previous;
} }
@ -146,12 +147,14 @@ public class Connection {
JsonObject metadata = new JsonObject(); JsonObject metadata = new JsonObject();
metadata.addProperty("wallTime", currentTimeMillis()); metadata.addProperty("wallTime", currentTimeMillis());
JsonArray stack = null; JsonArray stack = null;
if (apiName == null) { if (titleReported) {
metadata.addProperty("internal", true); metadata.addProperty("internal", true);
} else { } else {
metadata.addProperty("apiName", apiName); if (title != null) {
// All but first message in an API call are considered internal and will be hidden from the inspector. metadata.addProperty("title", title);
apiName = null; // All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
titleReported = true;
}
if (stackTraceCollector != null) { if (stackTraceCollector != null) {
stack = stackTraceCollector.currentStackTrace(); stack = stackTraceCollector.currentStackTrace();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
@ -373,9 +376,6 @@ public class Connection {
case "Stream": case "Stream":
result = new Stream(parent, type, guid, initializer); result = new Stream(parent, type, guid, initializer);
break; break;
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "SocksSupport": case "SocksSupport":
break; break;
case "Tracing": case "Tracing":
@ -384,6 +384,9 @@ public class Connection {
case "WebSocket": case "WebSocket":
result = new WebSocketImpl(parent, type, guid, initializer); result = new WebSocketImpl(parent, type, guid, initializer);
break; break;
case "WebSocketRoute":
result = new WebSocketRouteImpl(parent, type, guid, initializer);
break;
case "Worker": case "Worker":
result = new WorkerImpl(parent, type, guid, initializer); result = new WorkerImpl(parent, type, guid, initializer);
break; break;

View File

@ -20,13 +20,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage; import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle; import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl implements ConsoleMessage { public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection; private final Connection connection;
private PageImpl page; private PageImpl page;

View File

@ -34,18 +34,16 @@ class DialogImpl extends ChannelOwner implements Dialog {
@Override @Override
public void accept(String promptText) { public void accept(String promptText) {
withLogging("Dialog.accept", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
if (promptText != null) { if (promptText != null) {
params.addProperty("promptText", promptText); params.addProperty("promptText", promptText);
} }
sendMessage("accept", params); sendMessage("accept", params, NO_TIMEOUT);
});
} }
@Override @Override
public void dismiss() { public void dismiss() {
withLogging("Dialog.dismiss", () -> sendMessage("dismiss")); sendMessage("dismiss");
} }
@Override @Override

View File

@ -46,22 +46,22 @@ class DownloadImpl implements Download {
@Override @Override
public void cancel() { public void cancel() {
page.withLogging("Download.cancel", () -> artifact.cancel()); artifact.cancel();
} }
@Override @Override
public InputStream createReadStream() { public InputStream createReadStream() {
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream()); return artifact.createReadStream();
} }
@Override @Override
public void delete() { public void delete() {
page.withLogging("Download.delete", () -> artifact.delete()); artifact.delete();
} }
@Override @Override
public String failure() { public String failure() {
return page.withLogging("Download.failure", () -> artifact.failure()); return artifact.failure();
} }
@Override @Override
@ -71,11 +71,11 @@ class DownloadImpl implements Download {
@Override @Override
public Path path() { public Path path() {
return page.withLogging("Download.path", () -> artifact.pathAfterFinished()); return artifact.pathAfterFinished();
} }
@Override @Override
public void saveAs(Path path) { public void saveAs(Path path) {
page.withLogging("Download.saveAs", () -> artifact.saveAs(path)); artifact.saveAs(path);
} }
} }

View File

@ -40,8 +40,11 @@ import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG; import static com.microsoft.playwright.options.ScreenshotType.PNG;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle { public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
private final FrameImpl frame;
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
this.frame = (FrameImpl)parent;
} }
@Override @Override
@ -51,24 +54,21 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override @Override
public ElementHandle querySelector(String selector) { public ElementHandle querySelector(String selector) {
return withLogging("ElementHandle.querySelector", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params); JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) { if (element == null) {
return null; return null;
} }
return connection.getExistingObject(element.get("guid").getAsString()); return connection.getExistingObject(element.get("guid").getAsString());
});
} }
@Override @Override
public List<ElementHandle> querySelectorAll(String selector) { public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("ElementHandle.<", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params); JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements"); JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) { if (elements == null) {
return null; return null;
@ -78,78 +78,59 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString())); handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
} }
return handles; return handles;
});
} }
@Override @Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) { public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("ElementHandle.evalOnSelector", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params); JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
});
} }
@Override @Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) { public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("ElementHandle.evalOnSelectorAll", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params); JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
});
} }
@Override @Override
public BoundingBox boundingBox() { public BoundingBox boundingBox() {
return withLogging("ElementHandle.boundingBox", () -> {
JsonObject json = sendMessage("boundingBox").getAsJsonObject(); JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) { if (!json.has("value")) {
return null; return null;
} }
return gson().fromJson(json.get("value"), BoundingBox.class); return gson().fromJson(json.get("value"), BoundingBox.class);
});
} }
@Override @Override
public void check(CheckOptions options) { public void check(CheckOptions options) {
withLogging("ElementHandle.check", () -> checkImpl(options));
}
private void checkImpl(CheckOptions options) {
if (options == null) { if (options == null) {
options = new CheckOptions(); options = new CheckOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("check", params); sendMessage("check", params, frame.timeout(options.timeout));
} }
@Override @Override
public void click(ClickOptions options) { public void click(ClickOptions options) {
withLogging("ElementHandle.click", () -> clickImpl(options));
}
private void clickImpl(ClickOptions options) {
if (options == null) { if (options == null) {
options = new ClickOptions(); options = new ClickOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("click", params); sendMessage("click", params, frame.timeout(options.timeout));
} }
@Override @Override
public Frame contentFrame() { public Frame contentFrame() {
return withLogging("ElementHandle.contentFrame", () -> contentFrameImpl());
}
private Frame contentFrameImpl() {
JsonObject json = sendMessage("contentFrame").getAsJsonObject(); JsonObject json = sendMessage("contentFrame").getAsJsonObject();
if (!json.has("frame")) { if (!json.has("frame")) {
return null; return null;
@ -159,177 +140,132 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override @Override
public void dblclick(DblclickOptions options) { public void dblclick(DblclickOptions options) {
withLogging("ElementHandle.dblclick", () -> dblclickImpl(options));
}
private void dblclickImpl(DblclickOptions options) {
if (options == null) { if (options == null) {
options = new DblclickOptions(); options = new DblclickOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dblclick", params); sendMessage("dblclick", params, frame.timeout(options.timeout));
} }
@Override @Override
public void dispatchEvent(String type, Object eventInit) { public void dispatchEvent(String type, Object eventInit) {
withLogging("ElementHandle.dispatchEvent", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("type", type); params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit))); params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params); sendMessage("dispatchEvent", params, NO_TIMEOUT);
});
} }
@Override @Override
public void fill(String value, FillOptions options) { public void fill(String value, FillOptions options) {
withLogging("ElementHandle.fill", () -> fillImpl(value, options));
}
private void fillImpl(String value, FillOptions options) {
if (options == null) { if (options == null) {
options = new FillOptions(); options = new FillOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("value", value); params.addProperty("value", value);
sendMessage("fill", params); sendMessage("fill", params, frame.timeout(options.timeout));
} }
@Override @Override
public void focus() { public void focus() {
withLogging("ElementHandle.focus", () -> sendMessage("focus")); sendMessage("focus");
} }
@Override @Override
public String getAttribute(String name) { public String getAttribute(String name) {
return withLogging("ElementHandle.getAttribute", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("name", name); params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject(); JsonObject json = sendMessage("getAttribute", params, NO_TIMEOUT).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null; return json.has("value") ? json.get("value").getAsString() : null;
});
} }
@Override @Override
public void hover(HoverOptions options) { public void hover(HoverOptions options) {
withLogging("ElementHandle.hover", () -> hoverImpl(options));
}
private void hoverImpl(HoverOptions options) {
if (options == null) { if (options == null) {
options = new HoverOptions(); options = new HoverOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("hover", params); sendMessage("hover", params, frame.timeout(options.timeout));
} }
@Override @Override
public String innerHTML() { public String innerHTML() {
return withLogging("ElementHandle.innerHTML", () -> {
JsonObject json = sendMessage("innerHTML").getAsJsonObject(); JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
});
} }
@Override @Override
public String innerText() { public String innerText() {
return withLogging("ElementHandle.innerText", () -> {
JsonObject json = sendMessage("innerText").getAsJsonObject(); JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
});
} }
@Override @Override
public String inputValue(InputValueOptions options) { public String inputValue(InputValueOptions options) {
return withLogging("ElementHandle.inputValue", () -> inputValueImpl(options));
}
private String inputValueImpl(InputValueOptions options) {
if (options == null) { if (options == null) {
options = new InputValueOptions(); options = new InputValueOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params).getAsJsonObject(); JsonObject json = sendMessage("inputValue", params, NO_TIMEOUT).getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
} }
@Override @Override
public boolean isChecked() { public boolean isChecked() {
return withLogging("ElementHandle.isChecked", () -> {
JsonObject json = sendMessage("isChecked").getAsJsonObject(); JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public boolean isDisabled() { public boolean isDisabled() {
return withLogging("ElementHandle.isDisabled", () -> {
JsonObject json = sendMessage("isDisabled").getAsJsonObject(); JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public boolean isEditable() { public boolean isEditable() {
return withLogging("ElementHandle.isEditable", () -> {
JsonObject json = sendMessage("isEditable").getAsJsonObject(); JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return withLogging("ElementHandle.isEnabled", () -> {
JsonObject json = sendMessage("isEnabled").getAsJsonObject(); JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public boolean isHidden() { public boolean isHidden() {
return withLogging("ElementHandle.isHidden", () -> {
JsonObject json = sendMessage("isHidden").getAsJsonObject(); JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public boolean isVisible() { public boolean isVisible() {
return withLogging("ElementHandle.isVisible", () -> {
JsonObject json = sendMessage("isVisible").getAsJsonObject(); JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
});
} }
@Override @Override
public FrameImpl ownerFrame() { public FrameImpl ownerFrame() {
return withLogging("ElementHandle.ownerFrame", () -> {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject(); JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) { if (!json.has("frame")) {
return null; return null;
} }
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
});
} }
@Override @Override
public void press(String key, PressOptions options) { public void press(String key, PressOptions options) {
withLogging("ElementHandle.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
if (options == null) { if (options == null) {
options = new PressOptions(); options = new PressOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key); params.addProperty("key", key);
sendMessage("press", params); sendMessage("press", params, frame.timeout(options.timeout));
} }
@Override @Override
public byte[] screenshot(ScreenshotOptions options) { public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
}
private byte[] screenshotImpl(ScreenshotOptions options) {
if (options == null) { if (options == null) {
options = new ScreenshotOptions(); options = new ScreenshotOptions();
} }
@ -348,7 +284,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path"); params.remove("path");
JsonObject json = sendMessage("screenshot", params).getAsJsonObject(); JsonObject json = sendMessage("screenshot", params, frame.timeout(options.timeout)).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString()); byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
if (options.path != null) { if (options.path != null) {
@ -359,7 +295,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override @Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) { public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options)); if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params, frame.timeout(options.timeout));
} }
@Override @Override
@ -383,7 +323,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) { if (values != null) {
params.add("options", toSelectValueOrLabel(values)); params.add("options", toSelectValueOrLabel(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
@Override @Override
@ -392,13 +332,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
return selectOption(values, options); return selectOption(values, options);
} }
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params);
}
@Override @Override
public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) { public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) {
@ -409,7 +342,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) { if (values != null) {
params.add("options", gson().toJsonTree(values)); params.add("options", gson().toJsonTree(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
@Override @Override
@ -421,19 +354,21 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) { if (values != null) {
params.add("elements", Serialization.toProtocol(values)); params.add("elements", Serialization.toProtocol(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
private List<String> selectOption(JsonObject params) { private List<String> selectOption(JsonObject params, Double timeout) {
return withLogging("SelectOption", () -> { JsonObject json = sendMessage("selectOption", params, frame.timeout(timeout)).getAsJsonObject();
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values")); return parseStringList(json.getAsJsonArray("values"));
});
} }
@Override @Override
public void selectText(SelectTextOptions options) { public void selectText(SelectTextOptions options) {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options)); if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params, frame.timeout(options.timeout));
} }
@Override @Override
@ -450,20 +385,9 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
setInputFiles(new Path[]{files}, options); setInputFiles(new Path[]{files}, options);
} }
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params);
}
@Override @Override
public void setInputFiles(Path[] files, SetInputFilesOptions options) { public void setInputFiles(Path[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
FrameImpl frame = ownerFrame(); FrameImpl frame = ownerFrame();
if (frame == null) { if (frame == null) {
throw new Error("Cannot set input files to detached element"); throw new Error("Cannot set input files to detached element");
@ -473,7 +397,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, frame.page().context()); addFilePathUploadParams(files, params, frame.page().context());
sendMessage("setInputFiles", params); sendMessage("setInputFiles", params, frame.timeout(options.timeout));
} }
@Override @Override
@ -483,77 +407,51 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override @Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) { public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files); checkFilePayloadSize(files);
if (options == null) { if (options == null) {
options = new SetInputFilesOptions(); options = new SetInputFilesOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("payloads", Serialization.toJsonArray(files)); params.add("payloads", Serialization.toJsonArray(files));
sendMessage("setInputFiles", params); sendMessage("setInputFiles", params, frame.timeout(options.timeout));
} }
@Override @Override
public void tap(TapOptions options) { public void tap(TapOptions options) {
withLogging("ElementHandle.tap", () -> tapImpl(options));
}
private void tapImpl(TapOptions options) {
if (options == null) { if (options == null) {
options = new TapOptions(); options = new TapOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("tap", params); sendMessage("tap", params, frame.timeout(options.timeout));
} }
@Override @Override
public String textContent() { public String textContent() {
return withLogging("ElementHandle.textContent", () -> textContentImpl());
}
private String textContentImpl() {
return withLogging("ElementHandle.textContent", () -> {
JsonObject json = sendMessage("textContent").getAsJsonObject(); JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null; return json.has("value") ? json.get("value").getAsString() : null;
});
} }
@Override @Override
public void type(String text, TypeOptions options) { public void type(String text, TypeOptions options) {
withLogging("ElementHandle.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
if (options == null) { if (options == null) {
options = new TypeOptions(); options = new TypeOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text); params.addProperty("text", text);
sendMessage("type", params); sendMessage("type", params, frame.timeout(options.timeout));
} }
@Override @Override
public void uncheck(UncheckOptions options) { public void uncheck(UncheckOptions options) {
withLogging("ElementHandle.uncheck", () -> uncheckImpl(options));
}
private void uncheckImpl(UncheckOptions options) {
if (options == null) { if (options == null) {
options = new UncheckOptions(); options = new UncheckOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("uncheck", params); sendMessage("uncheck", params, frame.timeout(options.timeout));
} }
@Override @Override
public void waitForElementState(ElementState state, WaitForElementStateOptions options) { public void waitForElementState(ElementState state, WaitForElementStateOptions options) {
withLogging("ElementHandle.waitForElementState", () -> waitForElementStateImpl(state, options));
}
private void waitForElementStateImpl(ElementState state, WaitForElementStateOptions options) {
if (options == null) { if (options == null) {
options = new WaitForElementStateOptions(); options = new WaitForElementStateOptions();
} }
@ -562,7 +460,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state)); params.addProperty("state", toProtocol(state));
sendMessage("waitForElementState", params); sendMessage("waitForElementState", params, frame.timeout(options.timeout));
} }
private static String toProtocol(ElementState state) { private static String toProtocol(ElementState state) {
@ -571,16 +469,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override @Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) { public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("ElementHandle.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
private ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
if (options == null) { if (options == null) {
options = new WaitForSelectorOptions(); options = new WaitForSelectorOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonElement json = sendMessage("waitForSelector", params); JsonElement json = sendMessage("waitForSelector", params, frame.timeout(options.timeout)).getAsJsonObject();
JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) { if (element == null) {
return null; return null;

View File

@ -58,8 +58,7 @@ class FileChooserImpl implements FileChooser {
@Override @Override
public void setFiles(Path[] files, SetFilesOptions options) { public void setFiles(Path[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles", element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
} }
@Override @Override
@ -69,7 +68,6 @@ class FileChooserImpl implements FileChooser {
@Override @Override
public void setFiles(FilePayload[] files, SetFilesOptions options) { public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles", element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
} }
} }

View File

@ -74,16 +74,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) { public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
}
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
if (options == null) { if (options == null) {
options = new QuerySelectorOptions(); options = new QuerySelectorOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params); JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) { if (element == null) {
return null; return null;
@ -93,7 +89,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public List<ElementHandle> querySelectorAll(String selector) { public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector)); JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
} }
@Override @Override
@ -110,7 +117,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options)); return selectOptionImpl(selector, values, options);
} }
@Override @Override
@ -119,24 +126,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
return selectOption(selector, values, options); return selectOption(selector, values, options);
} }
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
}
@Override @Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) { public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options)); return evalOnSelectorImpl(selector, pageFunction, arg, options);
} }
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) { Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
@ -147,14 +140,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params); JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
} }
@Override @Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) { public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelectorAll", () -> evalOnSelectorAllImpl(selector, pageFunction, arg)); return evalOnSelectorAllImpl(selector, pageFunction, arg);
} }
Object evalOnSelectorAllImpl(String selector, String pageFunction, Object arg) { Object evalOnSelectorAllImpl(String selector, String pageFunction, Object arg) {
@ -162,14 +155,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params); JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
} }
@Override @Override
public ElementHandle addScriptTag(AddScriptTagOptions options){ public ElementHandle addScriptTag(AddScriptTagOptions options){
return withLogging("Frame.addScriptTag", () -> addScriptTagImpl(options)); return addScriptTagImpl(options);
} }
ElementHandle addScriptTagImpl(AddScriptTagOptions options) { ElementHandle addScriptTagImpl(AddScriptTagOptions options) {
@ -189,13 +182,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
content = addSourceUrlToScript(content, options.path); content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content); jsonOptions.addProperty("content", content);
} }
JsonElement json = sendMessage("addScriptTag", jsonOptions); JsonElement json = sendMessage("addScriptTag", jsonOptions, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
} }
@Override @Override
public ElementHandle addStyleTag(AddStyleTagOptions options){ public ElementHandle addStyleTag(AddStyleTagOptions options){
return withLogging("Frame.addStyleTag", () -> addStyleTagImpl(options)); return addStyleTagImpl(options);
} }
ElementHandle addStyleTagImpl(AddStyleTagOptions options) { ElementHandle addStyleTagImpl(AddStyleTagOptions options) {
@ -215,22 +208,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
content += "/*# sourceURL=" + options.path.toString().replace("\n", "") + "*/"; content += "/*# sourceURL=" + options.path.toString().replace("\n", "") + "*/";
jsonOptions.addProperty("content", content); jsonOptions.addProperty("content", content);
} }
JsonElement json = sendMessage("addStyleTag", jsonOptions); JsonElement json = sendMessage("addStyleTag", jsonOptions, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
} }
@Override @Override
public void check(String selector, CheckOptions options){ public void check(String selector, CheckOptions options){
withLogging("Frame.check", () -> checkImpl(selector, options));
}
void checkImpl(String selector, CheckOptions options) {
if (options == null) { if (options == null) {
options = new CheckOptions(); options = new CheckOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("check", params); sendMessage("check", params, timeout(options.timeout));
} }
@Override @Override
@ -240,7 +229,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public void click(String selector, ClickOptions options) { public void click(String selector, ClickOptions options) {
withLogging("Frame.click", () -> clickImpl(selector, options)); clickImpl(selector, options);
} }
void clickImpl(String selector, ClickOptions options) { void clickImpl(String selector, ClickOptions options) {
@ -249,38 +238,26 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("click", params); sendMessage("click", params, timeout(options.timeout));
} }
@Override @Override
public String content() { public String content() {
return withLogging("Frame.content", () -> contentImpl());
}
String contentImpl() {
return sendMessage("content").getAsJsonObject().get("value").getAsString(); return sendMessage("content").getAsJsonObject().get("value").getAsString();
} }
@Override @Override
public void dblclick(String selector, DblclickOptions options) { public void dblclick(String selector, DblclickOptions options) {
withLogging("Frame.dblclick", () -> dblclickImpl(selector, options));
}
void dblclickImpl(String selector, DblclickOptions options) {
if (options == null) { if (options == null) {
options = new DblclickOptions(); options = new DblclickOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("dblclick", params); sendMessage("dblclick", params, timeout(options.timeout));
} }
@Override @Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) { public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Frame.dispatchEvent", () -> dispatchEventImpl(selector, type, eventInit, options));
}
void dispatchEventImpl(String selector, String type, Object eventInit, DispatchEventOptions options) {
if (options == null) { if (options == null) {
options = new DispatchEventOptions(); options = new DispatchEventOptions();
} }
@ -288,71 +265,55 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("type", type); params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit))); params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params); sendMessage("dispatchEvent", params, timeout(options.timeout));
} }
@Override @Override
public Object evaluate(String expression, Object arg) { public Object evaluate(String expression, Object arg) {
return withLogging("Frame.evaluate", () -> evaluateImpl(expression, arg));
}
Object evaluateImpl(String expression, Object arg) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", expression); params.addProperty("expression", expression);
params.addProperty("world", "main"); params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params); JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
} }
@Override @Override
public JSHandle evaluateHandle(String pageFunction, Object arg) { public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Frame.evaluateHandle", () -> evaluateHandleImpl(pageFunction, arg));
}
JSHandle evaluateHandleImpl(String pageFunction, Object arg) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.addProperty("world", "main"); params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params); JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
} }
@Override @Override
public void fill(String selector, String value, FillOptions options) { public void fill(String selector, String value, FillOptions options) {
withLogging("Frame.fill", () -> fillImpl(selector, value, options));
}
void fillImpl(String selector, String value, FillOptions options) {
if (options == null) { if (options == null) {
options = new FillOptions(); options = new FillOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("value", value); params.addProperty("value", value);
sendMessage("fill", params); sendMessage("fill", params, timeout(options.timeout));
} }
@Override @Override
public void focus(String selector, FocusOptions options) { public void focus(String selector, FocusOptions options) {
withLogging("Frame.focus", () -> focusImpl(selector, options));
}
void focusImpl(String selector, FocusOptions options) {
if (options == null) { if (options == null) {
options = new FocusOptions(); options = new FocusOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("focus", params); sendMessage("focus", params, timeout(options.timeout));
} }
@Override @Override
public ElementHandle frameElement() { public ElementHandle frameElement() {
return withLogging("Frame.frameElement", () -> frameElementImpl()); JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
} }
@Override @Override
@ -360,14 +321,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
return new FrameLocatorImpl(this, selector); return new FrameLocatorImpl(this, selector);
} }
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
}
@Override @Override
public String getAttribute(String selector, String name, GetAttributeOptions options) { public String getAttribute(String selector, String name, GetAttributeOptions options) {
return withLogging("Frame.getAttribute", () -> getAttributeImpl(selector, name, options)); return getAttributeImpl(selector, name, options);
} }
@Override @Override
@ -442,7 +398,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("name", name); params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject(); JsonObject json = sendMessage("getAttribute", params, timeout(options.timeout)).getAsJsonObject();
if (json.has("value")) { if (json.has("value")) {
return json.get("value").getAsString(); return json.get("value").getAsString();
} }
@ -451,7 +407,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public ResponseImpl navigate(String url, NavigateOptions options) { public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> navigateImpl(url, options)); return navigateImpl(url, options);
} }
ResponseImpl navigateImpl(String url, NavigateOptions options) { ResponseImpl navigateImpl(String url, NavigateOptions options) {
@ -460,7 +416,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("url", url); params.addProperty("url", url);
JsonElement result = sendMessage("goto", params); JsonElement result = sendMessage("goto", params, navigationTimeout(options.timeout));
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response"); JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) { if (jsonResponse == null) {
return null; return null;
@ -470,7 +426,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public void hover(String selector, HoverOptions options) { public void hover(String selector, HoverOptions options) {
withLogging("Frame.hover", () -> hoverImpl(selector, options)); hoverImpl(selector, options);
} }
void hoverImpl(String selector, HoverOptions options) { void hoverImpl(String selector, HoverOptions options) {
@ -479,12 +435,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("hover", params); sendMessage("hover", params, timeout(options.timeout));
} }
@Override @Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) { public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options)); dragAndDropImpl(source, target, options);
} }
void dragAndDropImpl(String source, String target, DragAndDropOptions options) { void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
@ -494,12 +450,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source); params.addProperty("source", source);
params.addProperty("target", target); params.addProperty("target", target);
sendMessage("dragAndDrop", params); sendMessage("dragAndDrop", params, timeout(options.timeout));
} }
@Override @Override
public String innerHTML(String selector, InnerHTMLOptions options) { public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options)); return innerHTMLImpl(selector, options);
} }
String innerHTMLImpl(String selector, InnerHTMLOptions options) { String innerHTMLImpl(String selector, InnerHTMLOptions options) {
@ -508,13 +464,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("innerHTML", params).getAsJsonObject(); JsonObject json = sendMessage("innerHTML", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
} }
@Override @Override
public String innerText(String selector, InnerTextOptions options) { public String innerText(String selector, InnerTextOptions options) {
return withLogging("Frame.innerText", () -> innerTextImpl(selector, options)); return innerTextImpl(selector, options);
} }
String innerTextImpl(String selector, InnerTextOptions options) { String innerTextImpl(String selector, InnerTextOptions options) {
@ -523,13 +479,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("innerText", params).getAsJsonObject(); JsonObject json = sendMessage("innerText", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
} }
@Override @Override
public String inputValue(String selector, InputValueOptions options) { public String inputValue(String selector, InputValueOptions options) {
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options)); return inputValueImpl(selector, options);
} }
String inputValueImpl(String selector, InputValueOptions options) { String inputValueImpl(String selector, InputValueOptions options) {
@ -538,13 +494,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("inputValue", params).getAsJsonObject(); JsonObject json = sendMessage("inputValue", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString(); return json.get("value").getAsString();
} }
@Override @Override
public boolean isChecked(String selector, IsCheckedOptions options) { public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options)); return isCheckedImpl(selector, options);
} }
boolean isCheckedImpl(String selector, IsCheckedOptions options) { boolean isCheckedImpl(String selector, IsCheckedOptions options) {
@ -553,7 +509,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isChecked", params).getAsJsonObject(); JsonObject json = sendMessage("isChecked", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@ -564,7 +520,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public boolean isDisabled(String selector, IsDisabledOptions options) { public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled", () -> isDisabledImpl(selector, options)); return isDisabledImpl(selector, options);
} }
boolean isDisabledImpl(String selector, IsDisabledOptions options) { boolean isDisabledImpl(String selector, IsDisabledOptions options) {
@ -573,13 +529,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isDisabled", params).getAsJsonObject(); JsonObject json = sendMessage("isDisabled", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@Override @Override
public boolean isEditable(String selector, IsEditableOptions options) { public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable", () -> isEditableImpl(selector, options)); return isEditableImpl(selector, options);
} }
boolean isEditableImpl(String selector, IsEditableOptions options) { boolean isEditableImpl(String selector, IsEditableOptions options) {
@ -588,13 +544,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isEditable", params).getAsJsonObject(); JsonObject json = sendMessage("isEditable", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@Override @Override
public boolean isEnabled(String selector, IsEnabledOptions options) { public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled", () -> isEnabledImpl(selector, options)); return isEnabledImpl(selector, options);
} }
boolean isEnabledImpl(String selector, IsEnabledOptions options) { boolean isEnabledImpl(String selector, IsEnabledOptions options) {
@ -603,13 +559,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isEnabled", params).getAsJsonObject(); JsonObject json = sendMessage("isEnabled", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@Override @Override
public boolean isHidden(String selector, IsHiddenOptions options) { public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden", () -> isHiddenImpl(selector, options)); return isHiddenImpl(selector, options);
} }
boolean isHiddenImpl(String selector, IsHiddenOptions options) { boolean isHiddenImpl(String selector, IsHiddenOptions options) {
@ -618,13 +574,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isHidden", params).getAsJsonObject(); JsonObject json = sendMessage("isHidden", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@Override @Override
public boolean isVisible(String selector, IsVisibleOptions options) { public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options)); return isVisibleImpl(selector, options);
} }
@Override @Override
@ -638,7 +594,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject json = sendMessage("isVisible", params).getAsJsonObject(); JsonObject json = sendMessage("isVisible", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean(); return json.get("value").getAsBoolean();
} }
@ -659,7 +615,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public void press(String selector, String key, PressOptions options) { public void press(String selector, String key, PressOptions options) {
withLogging("Frame.press", () -> pressImpl(selector, key, options)); pressImpl(selector, key, options);
} }
void pressImpl(String selector, String key, PressOptions options) { void pressImpl(String selector, String key, PressOptions options) {
@ -669,12 +625,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("key", key); params.addProperty("key", key);
sendMessage("press", params); sendMessage("press", params, timeout(options.timeout));
} }
@Override @Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options)); return selectOptionImpl(selector, values, options);
} }
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) { List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
@ -686,7 +642,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) { if (values != null) {
params.add("options", gson().toJsonTree(values)); params.add("options", gson().toJsonTree(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
List<String> selectOptionImpl(String selector, String[] values, SelectOptionOptions options) { List<String> selectOptionImpl(String selector, String[] values, SelectOptionOptions options) {
@ -698,12 +654,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) { if (values != null) {
params.add("options", toSelectValueOrLabel(values)); params.add("options", toSelectValueOrLabel(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
@Override @Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options)); return selectOptionImpl(selector, values, options);
} }
List<String> selectOptionImpl(String selector, ElementHandle[] values, SelectOptionOptions options) { List<String> selectOptionImpl(String selector, ElementHandle[] values, SelectOptionOptions options) {
@ -715,30 +671,35 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) { if (values != null) {
params.add("elements", Serialization.toProtocol(values)); params.add("elements", Serialization.toProtocol(values));
} }
return selectOption(params); return selectOption(params, options.timeout);
} }
private List<String> selectOption(JsonObject params) { private List<String> selectOption(JsonObject params, Double timeout) {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject(); JsonObject json = sendMessage("selectOption", params, timeout(timeout)).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values")); return parseStringList(json.getAsJsonArray("values"));
} }
@Override @Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) { public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options)); setCheckedImpl(selector, checked, options);
} }
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) { void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) { if (checked) {
checkImpl(selector, convertType(options, CheckOptions.class)); check(selector, convertType(options, CheckOptions.class));
} else { } else {
uncheckImpl(selector, convertType(options, UncheckOptions.class)); uncheck(selector, convertType(options, UncheckOptions.class));
} }
} }
@Override @Override
public void setContent(String html, SetContentOptions options) { public void setContent(String html, SetContentOptions options) {
withLogging("Frame.setContent", () -> setContentImpl(html, options)); if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
sendMessage("setContent", params, navigationTimeout(options.timeout));
} }
@Override @Override
@ -746,18 +707,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
setInputFiles(selector, new Path[] {files}, options); setInputFiles(selector, new Path[] {files}, options);
} }
void setContentImpl(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
sendMessage("setContent", params);
}
@Override @Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) { public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options)); setInputFilesImpl(selector, files, options);
} }
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) { void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
@ -767,7 +719,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, page.context()); addFilePathUploadParams(files, params, page.context());
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("setInputFiles", params); sendMessage("setInputFiles", params, timeout(options.timeout));
} }
@Override @Override
@ -777,7 +729,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) { public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options)); setInputFilesImpl(selector, files, options);
} }
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) { void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
@ -788,73 +740,54 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.add("payloads", toJsonArray(files)); params.add("payloads", toJsonArray(files));
sendMessage("setInputFiles", params); sendMessage("setInputFiles", params, timeout(options.timeout));
} }
@Override @Override
public void tap(String selector, TapOptions options) { public void tap(String selector, TapOptions options) {
withLogging("Frame.tap", () -> tapImpl(selector, options));
}
void tapImpl(String selector, TapOptions options) {
if (options == null) { if (options == null) {
options = new TapOptions(); options = new TapOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("tap", params); sendMessage("tap", params, timeout(options.timeout));
} }
@Override @Override
public String textContent(String selector, TextContentOptions options) { public String textContent(String selector, TextContentOptions options) {
return withLogging("Frame.textContent", () -> textContentImpl(selector, options));
}
String textContentImpl(String selector, TextContentOptions options) {
if (options == null) { if (options == null) {
options = new TextContentOptions(); options = new TextContentOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
return sendMessage("textContent", params).getAsJsonObject().get("value").getAsString(); return sendMessage("textContent", params, timeout(options.timeout)).getAsJsonObject().get("value").getAsString();
} }
@Override @Override
public String title() { public String title() {
return withLogging("Frame.title", () -> titleImpl());
}
String titleImpl() {
JsonElement json = sendMessage("title"); JsonElement json = sendMessage("title");
return json.getAsJsonObject().get("value").getAsString(); return json.getAsJsonObject().get("value").getAsString();
} }
@Override @Override
public void type(String selector, String text, TypeOptions options) { public void type(String selector, String text, TypeOptions options) {
withLogging("Frame.type", () -> typeImpl(selector, text, options));
}
void typeImpl(String selector, String text, TypeOptions options) {
if (options == null) { if (options == null) {
options = new TypeOptions(); options = new TypeOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("text", text); params.addProperty("text", text);
sendMessage("type", params); sendMessage("type", params, timeout(options.timeout));
} }
@Override @Override
public void uncheck(String selector, UncheckOptions options) { public void uncheck(String selector, UncheckOptions options) {
withLogging("Frame.uncheck", () -> uncheckImpl(selector, options));
}
void uncheckImpl(String selector, UncheckOptions options) {
if (options == null) { if (options == null) {
options = new UncheckOptions(); options = new UncheckOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("uncheck", params); sendMessage("uncheck", params, timeout(options.timeout));
} }
@Override @Override
@ -864,17 +797,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) { public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Frame.waitForFunction", () -> waitForFunctionImpl(pageFunction, arg, options));
}
JSHandle waitForFunctionImpl(String pageFunction, Object arg, WaitForFunctionOptions options) {
if (options == null) { if (options == null) {
options = new WaitForFunctionOptions(); options = new WaitForFunctionOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("waitForFunction", params); JsonElement json = sendMessage("waitForFunction", params, timeout(options.timeout));
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle"); JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
return connection.getExistingObject(element.get("guid").getAsString()); return connection.getExistingObject(element.get("guid").getAsString());
} }
@ -1031,7 +960,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>(); List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) { if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url); matcher = UrlMatcher.forOneOf(page.context().baseUrl(), options.url, this.connection.localUtils, false);
} }
logger.log("waiting for navigation " + matcher); logger.log("waiting for navigation " + matcher);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger)); waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
@ -1043,10 +972,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) { public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Frame.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
return waitForSelectorImpl(selector, options, false); return waitForSelectorImpl(selector, options, false);
} }
@ -1057,7 +982,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("omitReturnValue", omitReturnValue); params.addProperty("omitReturnValue", omitReturnValue);
JsonElement json = sendMessage("waitForSelector", params); JsonElement json = sendMessage("waitForSelector", params, timeout(options.timeout));
JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) { if (element == null) {
return null; return null;
@ -1067,18 +992,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override @Override
public void waitForTimeout(double timeout) { public void waitForTimeout(double timeout) {
withLogging("Frame.waitForTimeout", () -> waitForTimeoutImpl(timeout));
}
void waitForTimeoutImpl(double timeout) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("timeout", timeout); params.addProperty("waitTimeout", timeout);
sendMessage("waitForTimeout", params); sendMessage("waitForTimeout", params, NO_TIMEOUT);
} }
@Override @Override
public void waitForURL(String url, WaitForURLOptions options) { public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(page.context().baseUrl, url), options); waitForURL(UrlMatcher.forGlob(page.context().baseUrl(), url, this.connection.localUtils, false), options);
} }
@Override @Override
@ -1113,14 +1034,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
int queryCount(String selector) { int queryCount(String selector) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
JsonObject result = sendMessage("queryCount", params).getAsJsonObject(); JsonObject result = sendMessage("queryCount", params, NO_TIMEOUT).getAsJsonObject();
return result.get("value").getAsInt(); return result.get("value").getAsInt();
} }
void highlightImpl(String selector) { void highlightImpl(String selector) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
sendMessage("highlight", params); sendMessage("highlight", params, NO_TIMEOUT);
} }
protected void handleEvent(String event, JsonObject params) { protected void handleEvent(String event, JsonObject params) {
@ -1151,4 +1072,30 @@ public class FrameImpl extends ChannelOwner implements Frame {
internalListeners.notify(InternalEventType.NAVIGATED, params); internalListeners.notify(InternalEventType.NAVIGATED, params);
} }
} }
protected double timeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.timeout(timeout);
}
return new TimeoutSettings().timeout(timeout);
}
protected double navigationTimeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.navigationTimeout(timeout);
}
return new TimeoutSettings().navigationTimeout(timeout);
}
FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
return withTitle(title, () -> expect(expression, options));
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", expression);
JsonElement json = sendMessage("expect", params, options.timeout);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
} }

View File

@ -136,6 +136,6 @@ class FrameLocatorImpl implements FrameLocator {
@Override @Override
public Locator owner() { public Locator owner() {
return new LocatorImpl(frame, frameSelector); return new LocatorImpl(frame, frameSelector, null);
} }
} }

View File

@ -26,6 +26,7 @@ import java.nio.file.Path;
import java.util.Base64; import java.util.Base64;
import java.util.Map; import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.LoggingSupport.*; import static com.microsoft.playwright.impl.LoggingSupport.*;
import static com.microsoft.playwright.impl.Serialization.fromNameValues; import static com.microsoft.playwright.impl.Serialization.fromNameValues;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
@ -41,7 +42,7 @@ public class HARRouter {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("file", harFile.toString()); params.addProperty("file", harFile.toString());
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject(); JsonObject json = localUtils.sendMessage("harOpen", params, NO_TIMEOUT).getAsJsonObject();
if (json.has("error")) { if (json.has("error")) {
throw new PlaywrightException(json.get("error").getAsString()); throw new PlaywrightException(json.get("error").getAsString());
} }
@ -61,7 +62,7 @@ public class HARRouter {
params.addProperty("postData", base64); params.addProperty("postData", base64);
} }
params.addProperty("isNavigationRequest", request.isNavigationRequest()); params.addProperty("isNavigationRequest", request.isNavigationRequest());
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject(); JsonObject response = localUtils.sendMessage("harLookup", params, NO_TIMEOUT).getAsJsonObject();
String action = response.get("action").getAsString(); String action = response.get("action").getAsString();
if ("redirect".equals(action)) { if ("redirect".equals(action)) {

View File

@ -41,42 +41,35 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
@Override @Override
public void dispose() { public void dispose() {
withLogging("JSHandle.dispose", () -> {
try { try {
sendMessage("dispose"); sendMessage("dispose");
} catch (TargetClosedError e) { } catch (TargetClosedError e) {
} }
});
} }
@Override @Override
public Object evaluate(String pageFunction, Object arg) { public Object evaluate(String pageFunction, Object arg) {
return withLogging("JSHandle.evaluate", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.addProperty("world", "main"); params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params); JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
});
} }
@Override @Override
public JSHandle evaluateHandle(String pageFunction, Object arg) { public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("JSHandle.evaluateHandle", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.addProperty("world", "main"); params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params); JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
});
} }
@Override @Override
public Map<String, JSHandle> getProperties() { public Map<String, JSHandle> getProperties() {
return withLogging("JSHandle.getProperties", () -> {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject(); JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>(); Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) { for (JsonElement e : json.getAsJsonArray("properties")) {
@ -85,26 +78,21 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
result.put(item.get("name").getAsString(), value); result.put(item.get("name").getAsString(), value);
} }
return result; return result;
});
} }
@Override @Override
public JSHandle getProperty(String propertyName) { public JSHandle getProperty(String propertyName) {
return withLogging("JSHandle.getProperty", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("name", propertyName); params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params).getAsJsonObject(); JsonObject json = sendMessage("getProperty", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
});
} }
@Override @Override
public Object jsonValue() { public Object jsonValue() {
return withLogging("JSHandle.jsonValue", () -> {
JsonObject json = sendMessage("jsonValue").getAsJsonObject(); JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
});
} }
@Override @Override

View File

@ -44,7 +44,7 @@ class JsonPipe extends ChannelOwner implements Transport {
checkIfClosed(); checkIfClosed();
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("message", message); params.add("message", message);
sendMessage("send", params); sendMessage("send", params, NO_TIMEOUT);
} }
@Override @Override

View File

@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.Keyboard; import com.microsoft.playwright.Keyboard;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl implements Keyboard { class KeyboardImpl implements Keyboard {
@ -30,25 +31,21 @@ class KeyboardImpl implements Keyboard {
@Override @Override
public void down(String key) { public void down(String key) {
page.withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("key", key); params.addProperty("key", key);
page.sendMessage("keyboardDown", params); page.sendMessage("keyboardDown", params, NO_TIMEOUT);
});
} }
@Override @Override
public void insertText(String text) { public void insertText(String text) {
page.withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("text", text); params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params); page.sendMessage("keyboardInsertText", params, NO_TIMEOUT);
});
} }
@Override @Override
public void press(String key, PressOptions options) { public void press(String key, PressOptions options) {
page.withLogging("Keyboard.press", () -> pressImpl(key, options)); pressImpl(key, options);
} }
private void pressImpl(String key, PressOptions options) { private void pressImpl(String key, PressOptions options) {
@ -57,12 +54,12 @@ class KeyboardImpl implements Keyboard {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key); params.addProperty("key", key);
page.sendMessage("keyboardPress", params); page.sendMessage("keyboardPress", params, NO_TIMEOUT);
} }
@Override @Override
public void type(String text, TypeOptions options) { public void type(String text, TypeOptions options) {
page.withLogging("Keyboard.type", () -> typeImpl(text, options)); typeImpl(text, options);
} }
private void typeImpl(String text, TypeOptions options) { private void typeImpl(String text, TypeOptions options) {
@ -71,15 +68,13 @@ class KeyboardImpl implements Keyboard {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text); params.addProperty("text", text);
page.sendMessage("keyboardType", params); page.sendMessage("keyboardType", params, NO_TIMEOUT);
} }
@Override @Override
public void up(String key) { public void up(String key) {
page.withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("key", key); params.addProperty("key", key);
page.sendMessage("keyboardUp", params); page.sendMessage("keyboardUp", params, NO_TIMEOUT);
});
} }
} }

View File

@ -17,6 +17,7 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -46,6 +47,9 @@ class ListenerCollection <EventType> {
} }
void add(EventType type, Consumer<?> listener) { void add(EventType type, Consumer<?> listener) {
if (listener == null) {
throw new PlaywrightException("Can't add a null listener");
}
List<Consumer<?>> list = listeners.get(type); List<Consumer<?>> list = listeners.get(type);
if (list == null) { if (list == null) {
list = new ArrayList<>(); list = new ArrayList<>();

View File

@ -21,10 +21,11 @@ import com.google.gson.JsonObject;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
class LocalUtils extends ChannelOwner { public class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) { LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
} }
@ -40,13 +41,13 @@ class LocalUtils extends ChannelOwner {
params.addProperty("mode", appendMode ? "append" : "write"); params.addProperty("mode", appendMode ? "append" : "write");
params.addProperty("stacksId", stacksId); params.addProperty("stacksId", stacksId);
params.addProperty("includeSources", includeSources); params.addProperty("includeSources", includeSources);
sendMessage("zip", params); sendMessage("zip", params, NO_TIMEOUT);
} }
void traceDiscarded(String stacksId) { void traceDiscarded(String stacksId) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("stacksId", stacksId); params.addProperty("stacksId", stacksId);
sendMessage("traceDiscarded", params); sendMessage("traceDiscarded", params, NO_TIMEOUT);
} }
String tracingStarted(String tracesDir, String traceName) { String tracingStarted(String tracesDir, String traceName) {
@ -55,7 +56,19 @@ class LocalUtils extends ChannelOwner {
params.addProperty("tracesDir", ""); params.addProperty("tracesDir", "");
} }
params.addProperty("traceName", traceName); params.addProperty("traceName", traceName);
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params).getAsJsonObject(); JsonObject json = connection.localUtils().sendMessage("tracingStarted", params, NO_TIMEOUT).getAsJsonObject();
return json.get("stacksId").getAsString(); return json.get("stacksId").getAsString();
} }
public Pattern globToRegex(String glob, String baseURL, boolean webSocketUrl) {
JsonObject params = new JsonObject();
params.addProperty("glob", glob);
if (baseURL != null) {
params.addProperty("baseURL", baseURL);
}
params.addProperty("webSocketUrl", webSocketUrl);
JsonObject json = connection.localUtils().sendMessage("globToRegex", params, NO_TIMEOUT).getAsJsonObject();
String regex = json.get("regex").getAsString();
return Pattern.compile(regex);
}
} }

View File

@ -20,21 +20,49 @@ import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions; import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.options.AriaRole; import com.microsoft.playwright.options.AriaRole;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.serializeArgument; import static com.microsoft.playwright.impl.Serialization.serializeArgument;
import static com.microsoft.playwright.impl.Utils.convertType; import static com.microsoft.playwright.impl.Utils.convertType;
public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions { public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions {
LocatorImpl actualLocator;
public LocatorAssertionsImpl(Locator locator) { public LocatorAssertionsImpl(Locator locator) {
this(locator, false); this(locator, false);
} }
private LocatorAssertionsImpl(Locator locator, boolean isNot) { private LocatorAssertionsImpl(Locator locator, boolean isNot) {
super((LocatorImpl) locator, isNot); super(isNot);
this.actualLocator = (LocatorImpl) locator;
}
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
return actualLocator.expect(expression, expectOptions, title);
}
@Override
public void containsClass(String classname, ContainsClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = classname;
expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
}
@Override
public void containsClass(List<String> classnames, ContainsClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : classnames) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
} }
@Override @Override
@ -44,7 +72,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true; expected.matchSubstring = true;
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
} }
@Override @Override
@ -53,7 +81,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true; expected.matchSubstring = true;
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
} }
@Override @Override
@ -67,7 +95,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
list.add(expected); list.add(expected);
} }
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class)); expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
} }
@Override @Override
@ -80,7 +108,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
list.add(expected); list.add(expected);
} }
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class)); expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
} }
@Override @Override
@ -88,14 +116,33 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = description; expected.string = description;
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class)); expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
} }
@Override @Override
public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOptions options) { public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class)); expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
}
@Override
public void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMessageOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = errorMessage;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
}
@Override
public void hasAccessibleErrorMessage(Pattern pattern, HasAccessibleErrorMessageOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
} }
@Override @Override
@ -103,14 +150,16 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = name; expected.string = name;
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class)); expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
} }
@Override @Override
public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options) { public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class)); expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
} }
@Override @Override
@ -138,20 +187,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) { if (expectedValue instanceof Pattern) {
message += " matching regex"; message += " matching regex";
} }
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions); expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Assert \"hasAttribute\"");
} }
@Override @Override
public void hasClass(String text, HasClassOptions options) { public void hasClass(String text, HasClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text; expected.string = text;
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
} }
@Override @Override
public void hasClass(Pattern pattern, HasClassOptions options) { public void hasClass(Pattern pattern, HasClassOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
} }
@Override @Override
@ -162,7 +211,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text; expected.string = text;
list.add(expected); list.add(expected);
} }
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
} }
@Override @Override
@ -172,7 +221,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
list.add(expected); list.add(expected);
} }
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
} }
@Override @Override
@ -183,7 +232,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class); FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = (double) count; commonOptions.expectedNumber = (double) count;
List<ExpectedTextValue> expectedText = null; List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions); expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Assert \"hasCount\"");
} }
@Override @Override
@ -209,20 +258,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) { if (expectedValue instanceof Pattern) {
message += " matching regex"; message += " matching regex";
} }
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions); expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Assert \"hasCSS\"");
} }
@Override @Override
public void hasId(String id, HasIdOptions options) { public void hasId(String id, HasIdOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = id; expected.string = id;
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
} }
@Override @Override
public void hasId(Pattern pattern, HasIdOptions options) { public void hasId(Pattern pattern, HasIdOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
} }
@Override @Override
@ -234,14 +283,14 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
commonOptions.expressionArg = name; commonOptions.expressionArg = name;
commonOptions.expectedValue = serializeArgument(value); commonOptions.expectedValue = serializeArgument(value);
List<ExpectedTextValue> list = null; List<ExpectedTextValue> list = null;
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions); expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Assert \"hasJSProperty\"");
} }
@Override @Override
public void hasRole(AriaRole role, HasRoleOptions options) { public void hasRole(AriaRole role, HasRoleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = role.toString().toLowerCase(); expected.string = role.toString().toLowerCase();
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Assert \"hasRole\"");
} }
@Override @Override
@ -251,7 +300,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = false; expected.matchSubstring = false;
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
} }
@Override @Override
@ -261,7 +310,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
// Just match substring, same as containsText. // Just match substring, same as containsText.
expected.matchSubstring = true; expected.matchSubstring = true;
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
} }
@Override @Override
@ -275,7 +324,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
list.add(expected); list.add(expected);
} }
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
} }
@Override @Override
@ -288,20 +337,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
list.add(expected); list.add(expected);
} }
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
} }
@Override @Override
public void hasValue(String value, HasValueOptions options) { public void hasValue(String value, HasValueOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value; expected.string = value;
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
} }
@Override @Override
public void hasValue(Pattern pattern, HasValueOptions options) { public void hasValue(Pattern pattern, HasValueOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
} }
@Override @Override
@ -312,7 +361,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text; expected.string = text;
list.add(expected); list.add(expected);
} }
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
} }
@Override @Override
@ -323,20 +372,50 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.matchSubstring = true; expected.matchSubstring = true;
list.add(expected); list.add(expected);
} }
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
}
@Override
public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snapshotOptions) {
if (snapshotOptions == null) {
snapshotOptions = new MatchesAriaSnapshotOptions();
}
FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
options.expectedValue = serializeArgument(expected);
expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
} }
@Override @Override
public void isChecked(IsCheckedOptions options) { public void isChecked(IsCheckedOptions options) {
boolean unchecked = options != null && options.checked != null && !options.checked; if (options == null) {
String expression = unchecked ? "to.be.unchecked" : "to.be.checked"; options = new IsCheckedOptions();
String message = "Locator expected to be " + (unchecked ? "un" : "") + "checked"; }
expectTrue(expression, message, convertType(options, FrameExpectOptions.class));
Map<String, Boolean> expectedValue = new HashMap<>();
if (options.indeterminate != null) {
expectedValue.put("indeterminate", options.indeterminate);
}
if (options.checked != null) {
expectedValue.put("checked", options.checked);
}
String expected;
if (options.indeterminate != null && options.indeterminate) {
expected = "indeterminate";
} else {
boolean unchecked = options.checked != null && !options.checked;
expected = unchecked ? "unchecked" : "checked";
}
String message = "Locator expected to be";
FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
expectOptions.expectedValue = serializeArgument(expectedValue);
expectImpl("to.be.checked", expectOptions, expected, message, "Assert \"isChecked\"");
} }
@Override @Override
public void isDisabled(IsDisabledOptions options) { public void isDisabled(IsDisabledOptions options) {
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class)); expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Assert \"isDisabled\"");
} }
@Override @Override
@ -344,12 +423,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class); FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean editable = options == null || options.editable == null || options.editable == true; boolean editable = options == null || options.editable == null || options.editable == true;
String message = "Locator expected to be " + (editable ? "editable" : "readonly"); String message = "Locator expected to be " + (editable ? "editable" : "readonly");
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions); expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Assert \"isEditable\"");
} }
@Override @Override
public void isEmpty(IsEmptyOptions options) { public void isEmpty(IsEmptyOptions options) {
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class)); expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Assert \"isEmpty\"");
} }
@Override @Override
@ -357,17 +436,17 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class); FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean enabled = options == null || options.enabled == null || options.enabled == true; boolean enabled = options == null || options.enabled == null || options.enabled == true;
String message = "Locator expected to be " + (enabled ? "enabled" : "disabled"); String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions); expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Assert \"isEnabled\"");
} }
@Override @Override
public void isFocused(IsFocusedOptions options) { public void isFocused(IsFocusedOptions options) {
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class)); expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Assert \"isFocused\"");
} }
@Override @Override
public void isHidden(IsHiddenOptions options) { public void isHidden(IsHiddenOptions options) {
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class)); expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Assert \"isHidden\"");
} }
@Override @Override
@ -376,7 +455,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (options != null && options.ratio != null) { if (options != null && options.ratio != null) {
expectOptions.expectedNumber = options.ratio; expectOptions.expectedNumber = options.ratio;
} }
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions); expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions, "Assert \"isInViewport\"");
} }
@Override @Override
@ -384,12 +463,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class); FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean visible = options == null || options.visible == null || options.visible == true; boolean visible = options == null || options.visible == null || options.visible == true;
String message = "Locator expected to be " + (visible ? "visible" : "hidden"); String message = "Locator expected to be " + (visible ? "visible" : "hidden");
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions); expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Assert \"isVisible\"");
} }
private void expectTrue(String expression, String message, FrameExpectOptions options) { private void expectTrue(String expression, String message, FrameExpectOptions options, String title) {
List<ExpectedTextValue> expectedText = null; List<ExpectedTextValue> expectedText = null;
expectImpl(expression, expectedText, null, message, options); expectImpl(expression, expectedText, null, message, options, title);
} }
@Override @Override
@ -402,6 +481,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class); FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean attached = options == null || options.attached == null || options.attached == true; boolean attached = options == null || options.attached == null || options.attached == true;
String message = "Locator expected to be " + (attached ? "attached" : "detached"); String message = "Locator expected to be " + (attached ? "attached" : "detached");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions); expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Assert \"isAttached\"");
} }
} }

View File

@ -21,29 +21,25 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.*; import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*; import com.microsoft.playwright.options.*;
import java.lang.reflect.Field;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*; import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType; import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class LocatorImpl implements Locator { class LocatorImpl implements Locator {
final FrameImpl frame; final FrameImpl frame;
final String selector; final String selector;
LocatorImpl(FrameImpl frame, String frameSelector) { LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this(frame, frameSelector, null); this(frame, selector, options, null);
} }
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) { private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Boolean visible) {
this.frame = frame; this.frame = frame;
if (options != null) { if (options != null) {
if (options.hasText != null) { if (options.hasText != null) {
@ -65,14 +61,14 @@ class LocatorImpl implements Locator {
selector += " >> internal:has-not=" + gson().toJson(locator.selector); selector += " >> internal:has-not=" + gson().toJson(locator.selector);
} }
} }
if (visible != null) {
selector += " >> visible=" + visible;
}
this.selector = selector; this.selector = selector;
} }
private static String escapeWithQuotes(String text) { private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options, String title) {
return gson().toJson(text); return frame.withTitle(title, () -> {
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class); ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout // TODO: support deadline based timeout
// Double timeout = null; // Double timeout = null;
@ -89,6 +85,7 @@ class LocatorImpl implements Locator {
handle.dispose(); handle.dispose();
} }
} }
});
} }
@Override @Override
@ -120,23 +117,30 @@ class LocatorImpl implements Locator {
} }
@Override @Override
public void blur(BlurOptions options) { public String ariaSnapshot(AriaSnapshotOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options)); if (options == null) {
options = new AriaSnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("ariaSnapshot", params, frame.timeout(options.timeout)).getAsJsonObject();
return result.get("snapshot").getAsString();
} }
private void blurImpl(BlurOptions options) { @Override
public void blur(BlurOptions options) {
if (options == null) { if (options == null) {
options = new BlurOptions(); options = new BlurOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector); params.addProperty("selector", selector);
params.addProperty("strict", true); params.addProperty("strict", true);
frame.sendMessage("blur", params); frame.sendMessage("blur", params, frame.timeout(options.timeout));
} }
@Override @Override
public BoundingBox boundingBox(BoundingBoxOptions options) { public BoundingBox boundingBox(BoundingBoxOptions options) {
return withElement((h, o) -> h.boundingBox(), options); return withElement((h, o) -> h.boundingBox(), options, "Bounding Box");
} }
@Override @Override
@ -149,7 +153,7 @@ class LocatorImpl implements Locator {
@Override @Override
public void clear(ClearOptions options) { public void clear(ClearOptions options) {
fill("", convertType(options, FillOptions.class)); frame.withTitle("Clear", () -> fill("", convertType(options, FillOptions.class)));
} }
@Override @Override
@ -165,6 +169,11 @@ class LocatorImpl implements Locator {
return frame.queryCount(selector); return frame.queryCount(selector);
} }
@Override
public Locator describe(String description) {
return locator(describeSelector(description));
}
@Override @Override
public void dblclick(DblclickOptions options) { public void dblclick(DblclickOptions options) {
if (options == null) { if (options == null) {
@ -214,7 +223,7 @@ class LocatorImpl implements Locator {
@Override @Override
public Object evaluate(String expression, Object arg, EvaluateOptions options) { public Object evaluate(String expression, Object arg, EvaluateOptions options) {
return withElement((h, o) -> h.evaluate(expression, arg), options); return withElement((h, o) -> h.evaluate(expression, arg), options, "Evaluate");
} }
@Override @Override
@ -224,7 +233,7 @@ class LocatorImpl implements Locator {
@Override @Override
public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) { public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
return withElement((h, o) -> h.evaluateHandle(expression, arg), options); return withElement((h, o) -> h.evaluateHandle(expression, arg), options, "Evaluate");
} }
@Override @Override
@ -237,7 +246,8 @@ class LocatorImpl implements Locator {
@Override @Override
public Locator filter(FilterOptions options) { public Locator filter(FilterOptions options) {
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class)); Boolean visible = (options == null) ? null : options.visible;
return new LocatorImpl(frame, selector, convertType(options, LocatorOptions.class), visible);
} }
@Override @Override
@ -468,7 +478,7 @@ class LocatorImpl implements Locator {
@Override @Override
public byte[] screenshot(ScreenshotOptions options) { public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class)); return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class), "Screenshot");
} }
@Override @Override
@ -476,7 +486,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> { withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o); h.scrollIntoViewIfNeeded(o);
return null; return null;
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class)); }, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class), "Scroll into view");
} }
@Override @Override
@ -532,7 +542,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> { withElement((h, o) -> {
h.selectText(o); h.selectText(o);
return null; return null;
}, convertType(options, ElementHandle.SelectTextOptions.class)); }, convertType(options, ElementHandle.SelectTextOptions.class), "Select text");
} }
@Override @Override
@ -612,11 +622,7 @@ class LocatorImpl implements Locator {
if (options == null) { if (options == null) {
options = new WaitForOptions(); options = new WaitForOptions();
} }
waitForImpl(options); frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true);
}
private void waitForImpl(WaitForOptions options) {
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
} }
@Override @Override
@ -638,8 +644,9 @@ class LocatorImpl implements Locator {
return frame.hashCode() ^ selector.hashCode(); return frame.hashCode() ^ selector.hashCode();
} }
FrameExpectResult expect(String expression, FrameExpectOptions options) { FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options)); options.selector = selector;
return frame.expect(expression, options, title);
} }
JsonObject toProtocol() { JsonObject toProtocol() {
@ -648,16 +655,4 @@ class LocatorImpl implements Locator {
result.addProperty("selector", selector); result.addProperty("selector", selector);
return result; 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;
}
} }

View File

@ -39,9 +39,12 @@ public class LocatorUtils {
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]"; return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
} }
static String describeSelector(String description) {
return "internal:describe=" + gson().toJson(description);
}
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) { static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName; return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
return getByAttributeTextSelector(testIdAttributeName, testId, true);
} }
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) { static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {

View File

@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;
class LoggingSupport { class LoggingSupport {
private static final boolean isEnabled; private static final boolean isEnabled;
@ -31,29 +30,6 @@ class LoggingSupport {
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern( private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC")); "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
void withLogging(String apiName, Runnable code) {
withLogging(apiName, () -> {
code.run();
return null;
});
}
<T> T withLogging(String apiName, Supplier<T> code) {
if (isEnabled) {
logApi("=> " + apiName + " started");
}
boolean success = false;
try {
T result = code.get();
success = true;
return result;
} finally {
if (isEnabled) {
logApi("<= " + apiName + (success ? " succeeded" : " failed"));
}
}
}
static void logWithTimestamp(String message) { static void logWithTimestamp(String message) {
// This matches log format produced by the server. // This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat); String timestamp = ZonedDateTime.now().format(timestampFormat);

View File

@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.Mouse; import com.microsoft.playwright.Mouse;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType; import static com.microsoft.playwright.impl.Utils.convertType;
@ -31,22 +32,18 @@ class MouseImpl implements Mouse {
@Override @Override
public void click(double x, double y, ClickOptions options) { public void click(double x, double y, ClickOptions options) {
page.withLogging("Mouse.click", () -> clickImpl(x, y, options));
}
private void clickImpl(double x, double y, ClickOptions options) {
if (options == null) { if (options == null) {
options = new ClickOptions(); options = new ClickOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x); params.addProperty("x", x);
params.addProperty("y", y); params.addProperty("y", y);
page.sendMessage("mouseClick", params); page.sendMessage("mouseClick", params, NO_TIMEOUT);
} }
@Override @Override
public void dblclick(double x, double y, DblclickOptions options) { public void dblclick(double x, double y, DblclickOptions options) {
page.withLogging("Mouse.dblclick", () -> dblclickImpl(x, y, options)); page.withTitle("Double click", () -> dblclickImpl(x, y, options));
} }
private void dblclickImpl(double x, double y, DblclickOptions options) { private void dblclickImpl(double x, double y, DblclickOptions options) {
@ -62,52 +59,38 @@ class MouseImpl implements Mouse {
@Override @Override
public void down(DownOptions options) { public void down(DownOptions options) {
page.withLogging("Mouse.down", () -> downImpl(options));
}
private void downImpl(DownOptions options) {
if (options == null) { if (options == null) {
options = new DownOptions(); options = new DownOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseDown", params); page.sendMessage("mouseDown", params, NO_TIMEOUT);
} }
@Override @Override
public void move(double x, double y, MoveOptions options) { public void move(double x, double y, MoveOptions options) {
page.withLogging("Mouse.move", () -> moveImpl(x, y, options));
}
private void moveImpl(double x, double y, MoveOptions options) {
if (options == null) { if (options == null) {
options = new MoveOptions(); options = new MoveOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x); params.addProperty("x", x);
params.addProperty("y", y); params.addProperty("y", y);
page.sendMessage("mouseMove", params); page.sendMessage("mouseMove", params, NO_TIMEOUT);
} }
@Override @Override
public void up(UpOptions options) { public void up(UpOptions options) {
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) { if (options == null) {
options = new UpOptions(); options = new UpOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseUp", params); page.sendMessage("mouseUp", params, NO_TIMEOUT);
}
@Override
public void wheel(double deltaX, double deltaY) {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params, NO_TIMEOUT);
} }
} }

View File

@ -21,7 +21,6 @@ import com.microsoft.playwright.assertions.PageAssertions;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorAssertionsImpl.shouldIgnoreCase;
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl; import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
import static com.microsoft.playwright.impl.Utils.convertType; import static com.microsoft.playwright.impl.Utils.convertType;
@ -33,39 +32,45 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
} }
private PageAssertionsImpl(Page page, boolean isNot) { private PageAssertionsImpl(Page page, boolean isNot) {
super((LocatorImpl) page.locator(":root"), isNot); super(isNot);
this.actualPage = (PageImpl) page; this.actualPage = (PageImpl) page;
} }
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
FrameImpl frame = (FrameImpl) actualPage.mainFrame();
return frame.expect(expression, expectOptions, title);
}
@Override @Override
public void hasTitle(String title, HasTitleOptions options) { public void hasTitle(String title, HasTitleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
expected.string = title; expected.string = title;
expected.normalizeWhiteSpace = true; expected.normalizeWhiteSpace = true;
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
} }
@Override @Override
public void hasTitle(Pattern pattern, HasTitleOptions options) { public void hasTitle(Pattern pattern, HasTitleOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
} }
@Override @Override
public void hasURL(String url, HasURLOptions options) { public void hasURL(String url, HasURLOptions options) {
ExpectedTextValue expected = new ExpectedTextValue(); ExpectedTextValue expected = new ExpectedTextValue();
if (actualPage.context().baseUrl != null) { if (actualPage.context().baseUrl() != null) {
url = resolveUrl(actualPage.context().baseUrl, url); url = resolveUrl(actualPage.context().baseUrl(), url);
} }
expected.string = url; expected.string = url;
expected.ignoreCase = shouldIgnoreCase(options); expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
} }
@Override @Override
public void hasURL(Pattern pattern, HasURLOptions options) { public void hasURL(Pattern pattern, HasURLOptions options) {
ExpectedTextValue expected = expectedRegex(pattern); ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class)); expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
} }
@Override @Override

View File

@ -48,6 +48,7 @@ public class PageImpl extends ChannelOwner implements Page {
final Waitable<?> waitableClosedOrCrashed; final Waitable<?> waitableClosedOrCrashed;
private ViewportSize viewport; private ViewportSize viewport;
private final Router routes = new Router(); private final Router routes = new Router();
private final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private final Set<FrameImpl> frames = new LinkedHashSet<>(); private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final Map<Integer, LocatorHandler> locatorHandlers = new HashMap<>(); private final Map<Integer, LocatorHandler> locatorHandlers = new HashMap<>();
@ -94,7 +95,7 @@ public class PageImpl extends ChannelOwner implements Page {
BrowserContextImpl ownedContext; BrowserContextImpl ownedContext;
private boolean isClosed; private boolean isClosed;
final Set<Worker> workers = new HashSet<>(); final Set<Worker> workers = new HashSet<>();
private final TimeoutSettings timeoutSettings; protected final TimeoutSettings timeoutSettings;
private VideoImpl video; private VideoImpl video;
private final PageImpl opener; private final PageImpl opener;
private String closeReason; private String closeReason;
@ -212,6 +213,11 @@ public class PageImpl extends ChannelOwner implements Page {
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) { if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
browserContext.handleRoute(route); browserContext.handleRoute(route);
} }
} else if ("webSocketRoute".equals(event)) {
WebSocketRouteImpl route = connection.getExistingObject(params.getAsJsonObject("webSocketRoute").get("guid").getAsString());
if (!webSocketRoutes.handle(route)) {
browserContext.handleWebSocketRoute(route);
}
} else if ("video".equals(event)) { } else if ("video".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString(); String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid); ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
@ -540,7 +546,7 @@ public class PageImpl extends ChannelOwner implements Page {
ownedContext.close(); ownedContext.close();
} else { } else {
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params); sendMessage("close", params, NO_TIMEOUT);
} }
} catch (PlaywrightException exception) { } catch (PlaywrightException exception) {
if (isSafeCloseError(exception) && (options.runBeforeUnload == null || !options.runBeforeUnload)) { if (isSafeCloseError(exception) && (options.runBeforeUnload == null || !options.runBeforeUnload)) {
@ -552,13 +558,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) { public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl( return mainFrame.querySelector(
selector, convertType(options, Frame.QuerySelectorOptions.class))); selector, convertType(options, Frame.QuerySelectorOptions.class));
} }
@Override @Override
public List<ElementHandle> querySelectorAll(String selector) { public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector)); return mainFrame.querySelectorAll(selector);
} }
@Override @Override
@ -574,17 +580,15 @@ public class PageImpl extends ChannelOwner implements Page {
return; return;
} }
AddLocatorHandlerOptions finalOptions = options; AddLocatorHandlerOptions finalOptions = options;
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector); params.addProperty("selector", locatorImpl.selector);
if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) { if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) {
params.addProperty("noWaitAfter", true); params.addProperty("noWaitAfter", true);
} }
params.addProperty("selector", locatorImpl.selector); params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params); JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params, NO_TIMEOUT);
int uid = json.get("uid").getAsInt(); int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times)); locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times));
});
} }
@Override @Override
@ -595,7 +599,7 @@ public class PageImpl extends ChannelOwner implements Page {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("uid", entry.getKey()); params.addProperty("uid", entry.getKey());
try { try {
sendMessage("unregisterLocatorHandler", params); sendMessage("unregisterLocatorHandler", params, NO_TIMEOUT);
} catch (PlaywrightException e) { } catch (PlaywrightException e) {
} }
} }
@ -620,23 +624,22 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) { public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl( return mainFrame.evalOnSelectorImpl(
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class))); selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class));
} }
@Override @Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) { public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("Page.evalOnSelectorAll", () -> mainFrame.evalOnSelectorAllImpl(selector, pageFunction, arg)); return mainFrame.evalOnSelectorAllImpl(selector, pageFunction, arg);
} }
@Override @Override
public void addInitScript(String script) { public void addInitScript(String script) {
withLogging("Page.addInitScript", () -> addInitScriptImpl(script)); addInitScriptImpl(script);
} }
@Override @Override
public void addInitScript(Path path) { public void addInitScript(Path path) {
withLogging("Page.addInitScript", () -> {
try { try {
byte[] bytes = readAllBytes(path); byte[] bytes = readAllBytes(path);
String script = addSourceUrlToScript(new String(bytes, UTF_8), path); String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
@ -644,47 +647,42 @@ public class PageImpl extends ChannelOwner implements Page {
} catch (IOException e) { } catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e); throw new PlaywrightException("Failed to read script from file", e);
} }
});
} }
private void addInitScriptImpl(String script) { private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("source", script); params.addProperty("source", script);
sendMessage("addInitScript", params); sendMessage("addInitScript", params, NO_TIMEOUT);
} }
@Override @Override
public ElementHandle addScriptTag(AddScriptTagOptions options) { public ElementHandle addScriptTag(AddScriptTagOptions options) {
return withLogging("Page.addScriptTag", return mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class));
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
} }
@Override @Override
public ElementHandle addStyleTag(AddStyleTagOptions options) { public ElementHandle addStyleTag(AddStyleTagOptions options) {
return withLogging("Page.addStyleTag", return mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class));
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
} }
@Override @Override
public void bringToFront() { public void bringToFront() {
withLogging("Page.bringToFront", () -> sendMessage("bringToFront")); sendMessage("bringToFront");
} }
@Override @Override
public void check(String selector, CheckOptions options) { public void check(String selector, CheckOptions options) {
withLogging("Page.check", mainFrame.check(selector, convertType(options, Frame.CheckOptions.class));
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
} }
@Override @Override
public void click(String selector, ClickOptions options) { public void click(String selector, ClickOptions options) {
withLogging("Page.click", mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class));
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
} }
@Override @Override
public String content() { public String content() {
return withLogging("Page.content", () -> mainFrame.contentImpl()); return mainFrame.content();
} }
@Override @Override
@ -694,19 +692,17 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void dblclick(String selector, DblclickOptions options) { public void dblclick(String selector, DblclickOptions options) {
withLogging("Page.dblclick", mainFrame.dblclick(selector, convertType(options, Frame.DblclickOptions.class));
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
} }
@Override @Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) { public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Page.dispatchEvent", mainFrame.dispatchEvent(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class));
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
} }
@Override @Override
public void emulateMedia(EmulateMediaOptions options) { public void emulateMedia(EmulateMediaOptions options) {
withLogging("Page.emulateMedia", () -> emulateMediaImpl(options)); emulateMediaImpl(options);
} }
private void emulateMediaImpl(EmulateMediaOptions options) { private void emulateMediaImpl(EmulateMediaOptions options) {
@ -714,22 +710,22 @@ public class PageImpl extends ChannelOwner implements Page {
options = new EmulateMediaOptions(); options = new EmulateMediaOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("emulateMedia", params); sendMessage("emulateMedia", params, NO_TIMEOUT);
} }
@Override @Override
public Object evaluate(String expression, Object arg) { public Object evaluate(String expression, Object arg) {
return withLogging("Page.evaluate", () -> mainFrame.evaluateImpl(expression, arg)); return mainFrame.evaluate(expression, arg);
} }
@Override @Override
public JSHandle evaluateHandle(String pageFunction, Object arg) { public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Page.evaluateHandle", () -> mainFrame.evaluateHandleImpl(pageFunction, arg)); return mainFrame.evaluateHandle(pageFunction, arg);
} }
@Override @Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) { public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
withLogging("Page.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options)); exposeBindingImpl(name, playwrightBinding, options);
} }
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) { private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
@ -746,25 +742,22 @@ public class PageImpl extends ChannelOwner implements Page {
if (options != null && options.handle != null && options.handle) { if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true); params.addProperty("needsHandle", true);
} }
sendMessage("exposeBinding", params); sendMessage("exposeBinding", params, NO_TIMEOUT);
} }
@Override @Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) { public void exposeFunction(String name, FunctionCallback playwrightFunction) {
withLogging("Page.exposeFunction", exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
} }
@Override @Override
public void fill(String selector, String value, FillOptions options) { public void fill(String selector, String value, FillOptions options) {
withLogging("Page.fill", mainFrame.fill(selector, value, convertType(options, Frame.FillOptions.class));
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
} }
@Override @Override
public void focus(String selector, FocusOptions options) { public void focus(String selector, FocusOptions options) {
withLogging("Page.focus", mainFrame.focus(selector, convertType(options, Frame.FocusOptions.class));
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
} }
@Override @Override
@ -779,7 +772,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Frame frameByUrl(String glob) { public Frame frameByUrl(String glob) {
return frameFor(new UrlMatcher(browserContext.baseUrl, glob)); return frameFor(UrlMatcher.forGlob(browserContext.baseUrl(), glob, this.connection.localUtils, false));
} }
@Override @Override
@ -813,89 +806,77 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public String getAttribute(String selector, String name, GetAttributeOptions options) { public String getAttribute(String selector, String name, GetAttributeOptions options) {
return withLogging("Page.getAttribute", return mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class));
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
} }
@Override @Override
public Locator getByAltText(String text, GetByAltTextOptions options) { public Locator getByAltText(String text, GetByAltTextOptions options) {
return withLogging("Page.getAttribute", return mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class));
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
} }
@Override @Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) { public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return withLogging("Page.getByAltText", return mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class));
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
} }
@Override @Override
public Locator getByLabel(String text, GetByLabelOptions options) { public Locator getByLabel(String text, GetByLabelOptions options) {
return withLogging("Page.getByLabel", return mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class));
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
} }
@Override @Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) { public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return withLogging("Page.getByLabel", return mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class));
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
} }
@Override @Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) { public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return withLogging("Page.getByPlaceholder", return mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class));
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
} }
@Override @Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) { public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return withLogging("Page.getByPlaceholder", return mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class));
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
} }
@Override @Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) { public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return withLogging("Page.getByRole", return mainFrame.getByRole(role, convertType(options, Frame.GetByRoleOptions.class));
() -> mainFrame.getByRole(role, convertType(options, Frame.GetByRoleOptions.class)));
} }
@Override @Override
public Locator getByTestId(String testId) { public Locator getByTestId(String testId) {
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId)); return mainFrame.getByTestId(testId);
} }
@Override @Override
public Locator getByTestId(Pattern testId) { public Locator getByTestId(Pattern testId) {
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId)); return mainFrame.getByTestId(testId);
} }
@Override @Override
public Locator getByText(String text, GetByTextOptions options) { public Locator getByText(String text, GetByTextOptions options) {
return withLogging("Page.getByText", return mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class));
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
} }
@Override @Override
public Locator getByText(Pattern text, GetByTextOptions options) { public Locator getByText(Pattern text, GetByTextOptions options) {
return withLogging("Page.getByText", return mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class));
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
} }
@Override @Override
public Locator getByTitle(String text, GetByTitleOptions options) { public Locator getByTitle(String text, GetByTitleOptions options) {
return withLogging("Page.getByTitle", return mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class));
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
} }
@Override @Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) { public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return withLogging("Page.getByTitle", return mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class));
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
} }
@Override @Override
public Response goBack(GoBackOptions options) { public Response goBack(GoBackOptions options) {
return withLogging("Page.goBack", () -> goBackImpl(options)); return goBackImpl(options);
} }
Response goBackImpl(GoBackOptions options) { Response goBackImpl(GoBackOptions options) {
@ -903,7 +884,7 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoBackOptions(); options = new GoBackOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("goBack", params).getAsJsonObject(); JsonObject json = sendMessage("goBack", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
if (json.has("response")) { if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
} }
@ -912,7 +893,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Response goForward(GoForwardOptions options) { public Response goForward(GoForwardOptions options) {
return withLogging("Page.goForward", () -> goForwardImpl(options)); return goForwardImpl(options);
} }
Response goForwardImpl(GoForwardOptions options) { Response goForwardImpl(GoForwardOptions options) {
@ -920,50 +901,51 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoForwardOptions(); options = new GoForwardOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("goForward", params).getAsJsonObject(); JsonObject json = sendMessage("goForward", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
if (json.has("response")) { if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
} }
return null; return null;
} }
@Override
public void requestGC() {
sendMessage("requestGC");
}
@Override @Override
public ResponseImpl navigate(String url, NavigateOptions options) { public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class))); return mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class));
} }
@Override @Override
public void hover(String selector, HoverOptions options) { public void hover(String selector, HoverOptions options) {
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class))); mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class));
} }
@Override @Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) { public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class))); mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class));
} }
@Override @Override
public String innerHTML(String selector, InnerHTMLOptions options) { public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Page.innerHTML", return mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class));
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
} }
@Override @Override
public String innerText(String selector, InnerTextOptions options) { public String innerText(String selector, InnerTextOptions options) {
return withLogging("Page.innerText", return mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class));
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
} }
@Override @Override
public String inputValue(String selector, InputValueOptions options) { public String inputValue(String selector, InputValueOptions options) {
return withLogging("Page.inputValue", return mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class));
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
} }
@Override @Override
public boolean isChecked(String selector, IsCheckedOptions options) { public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", return mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class));
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
} }
@Override @Override
@ -973,32 +955,27 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public boolean isDisabled(String selector, IsDisabledOptions options) { public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled", return mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class));
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
} }
@Override @Override
public boolean isEditable(String selector, IsEditableOptions options) { public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable", return mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class));
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
} }
@Override @Override
public boolean isEnabled(String selector, IsEnabledOptions options) { public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled", return mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class));
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
} }
@Override @Override
public boolean isHidden(String selector, IsHiddenOptions options) { public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden", return mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class));
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
} }
@Override @Override
public boolean isVisible(String selector, IsVisibleOptions options) { public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible", return mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class));
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
} }
@Override @Override
@ -1031,23 +1008,22 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void pause() { public void pause() {
withLogging("Page.pause", () -> { TimeoutSettings settings = browserContext.timeoutSettings;
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout(); Double defaultNavigationTimeout = settings.defaultNavigationTimeout();
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout(); Double defaultTimeout = settings.defaultTimeout();
browserContext.setDefaultNavigationTimeoutImpl(0.0); settings.setDefaultNavigationTimeout(0.0);
browserContext.setDefaultTimeoutImpl(0.0); settings.setDefaultTimeout(0.0);
try { try {
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed))); runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
} finally { } finally {
browserContext.setDefaultNavigationTimeoutImpl(defaultNavigationTimeout); settings.setDefaultNavigationTimeout(defaultNavigationTimeout);
browserContext.setDefaultTimeoutImpl(defaultTimeout); settings.setDefaultTimeout(defaultTimeout);
} }
});
} }
@Override @Override
public byte[] pdf(PdfOptions options) { public byte[] pdf(PdfOptions options) {
return withLogging("Page.pdf", () -> pdfImpl(options)); return pdfImpl(options);
} }
private byte[] pdfImpl(PdfOptions options) { private byte[] pdfImpl(PdfOptions options) {
@ -1056,7 +1032,7 @@ public class PageImpl extends ChannelOwner implements Page {
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path"); params.remove("path");
JsonObject json = sendMessage("pdf", params).getAsJsonObject(); JsonObject json = sendMessage("pdf", params, NO_TIMEOUT).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("pdf").getAsString()); byte[] buffer = Base64.getDecoder().decode(json.get("pdf").getAsString());
if (options.path != null) { if (options.path != null) {
Utils.writeToFile(buffer, options.path); Utils.writeToFile(buffer, options.path);
@ -1066,13 +1042,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void press(String selector, String key, PressOptions options) { public void press(String selector, String key, PressOptions options) {
withLogging("Page.press", mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class));
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
} }
@Override @Override
public Response reload(ReloadOptions options) { public Response reload(ReloadOptions options) {
return withLogging("Page.reload", () -> reloadImpl(options)); return reloadImpl(options);
} }
@Override @Override
@ -1085,7 +1060,7 @@ public class PageImpl extends ChannelOwner implements Page {
options = new ReloadOptions(); options = new ReloadOptions();
} }
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("reload", params).getAsJsonObject(); JsonObject json = sendMessage("reload", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
if (json.has("response")) { if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
} }
@ -1094,7 +1069,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void route(String url, Consumer<Route> handler, RouteOptions options) { public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(browserContext.baseUrl, url), handler, options); route(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler, options);
} }
@Override @Override
@ -1113,25 +1088,43 @@ public class PageImpl extends ChannelOwner implements Page {
options = new RouteFromHAROptions(); options = new RouteFromHAROptions();
} }
if (options.update != null && options.update) { if (options.update != null && options.update) {
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class)); browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class), null);
return; return;
} }
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url); UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl(), options.url, this.connection.localUtils, false);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound); HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose()); onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null); route(matcher, route -> harRouter.handle(route), null);
} }
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) { private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("Page.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times); routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns(); updateInterceptionPatterns();
}); }
@Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, true), handler);
}
@Override
public void routeWebSocket(Pattern pattern, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(pattern), handler);
}
@Override
public void routeWebSocket(Predicate<String> predicate, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(predicate), handler);
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
} }
@Override @Override
public byte[] screenshot(ScreenshotOptions options) { public byte[] screenshot(ScreenshotOptions options) {
return withLogging("Page.screenshot", () -> screenshotImpl(options)); return screenshotImpl(options);
} }
@Override @Override
@ -1148,8 +1141,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption", return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
} }
@Override @Override
@ -1175,19 +1167,9 @@ public class PageImpl extends ChannelOwner implements Page {
} }
} }
} }
List<Locator> mask = options.mask;
options.mask = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonObject params = gson().toJsonTree(options).getAsJsonObject();
options.mask = mask;
params.remove("path"); params.remove("path");
if (mask != null) { JsonObject json = sendMessage("screenshot", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
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()); byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
if (options.path != null) { if (options.path != null) {
@ -1198,51 +1180,36 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption", return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
} }
@Override @Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) { public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Page.selectOption", return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
} }
@Override @Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) { public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Page.setChecked", mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class));
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
} }
@Override @Override
public void setContent(String html, SetContentOptions options) { public void setContent(String html, SetContentOptions options) {
withLogging("Page.setContent", mainFrame.setContent(html, convertType(options, Frame.SetContentOptions.class));
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
} }
@Override @Override
public void setDefaultNavigationTimeout(double timeout) { public void setDefaultNavigationTimeout(double timeout) {
withLogging("Page.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout); timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
} }
@Override @Override
public void setDefaultTimeout(double timeout) { public void setDefaultTimeout(double timeout) {
withLogging("Page.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout); timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
} }
@Override @Override
public void setExtraHTTPHeaders(Map<String, String> headers) { public void setExtraHTTPHeaders(Map<String, String> headers) {
withLogging("Page.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray(); JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) { for (Map.Entry<String, String> e : headers.entrySet()) {
@ -1252,8 +1219,7 @@ public class PageImpl extends ChannelOwner implements Page {
jsonHeaders.add(header); jsonHeaders.add(header);
} }
params.add("headers", jsonHeaders); params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params); sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
});
} }
@Override @Override
@ -1263,8 +1229,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) { public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles", mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class));
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
} }
@Override @Override
@ -1274,35 +1239,30 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) { public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Page.setInputFiles", mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class));
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
} }
@Override @Override
public void setViewportSize(int width, int height) { public void setViewportSize(int width, int height) {
withLogging("Page.setViewportSize", () -> {
viewport = new ViewportSize(width, height); viewport = new ViewportSize(width, height);
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.add("viewportSize", gson().toJsonTree(viewport)); params.add("viewportSize", gson().toJsonTree(viewport));
sendMessage("setViewportSize", params); sendMessage("setViewportSize", params, NO_TIMEOUT);
});
} }
@Override @Override
public void tap(String selector, TapOptions options) { public void tap(String selector, TapOptions options) {
withLogging("Page.tap", mainFrame.tap(selector, convertType(options, Frame.TapOptions.class));
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
} }
@Override @Override
public String textContent(String selector, TextContentOptions options) { public String textContent(String selector, TextContentOptions options) {
return withLogging("Page.textContent", return mainFrame.textContent(selector, convertType(options, Frame.TextContentOptions.class));
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
} }
@Override @Override
public String title() { public String title() {
return withLogging("Page.title", () -> mainFrame.titleImpl()); return mainFrame.title();
} }
@Override @Override
@ -1312,27 +1272,23 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void type(String selector, String text, TypeOptions options) { public void type(String selector, String text, TypeOptions options) {
withLogging("Page.type", mainFrame.type(selector, text, convertType(options, Frame.TypeOptions.class));
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
} }
@Override @Override
public void uncheck(String selector, UncheckOptions options) { public void uncheck(String selector, UncheckOptions options) {
withLogging("Page.uncheck", mainFrame.uncheck(selector, convertType(options, Frame.UncheckOptions.class));
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
} }
@Override @Override
public void unrouteAll() { public void unrouteAll() {
withLogging("Page.unrouteAll", () -> {
routes.removeAll(); routes.removeAll();
updateInterceptionPatterns(); updateInterceptionPatterns();
});
} }
@Override @Override
public void unroute(String url, Consumer<Route> handler) { public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(browserContext.baseUrl, url), handler); unroute(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler);
} }
@Override @Override
@ -1346,14 +1302,16 @@ public class PageImpl extends ChannelOwner implements Page {
} }
private void unroute(UrlMatcher matcher, Consumer<Route> handler) { private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("Page.unroute", () -> {
routes.remove(matcher, handler); routes.remove(matcher, handler);
updateInterceptionPatterns(); updateInterceptionPatterns();
});
} }
private void updateInterceptionPatterns() { private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns()); sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
} }
@Override @Override
@ -1374,7 +1332,7 @@ public class PageImpl extends ChannelOwner implements Page {
// Note: we are creating Video object lazily, because we do not know // Note: we are creating Video object lazily, because we do not know
// BrowserContextOptions when constructing the page - it is assigned // BrowserContextOptions when constructing the page - it is assigned
// too late during launchPersistentContext. // too late during launchPersistentContext.
if (browserContext.videosDir == null) { if (browserContext.videosDir() == null) {
return null; return null;
} }
return forceVideo(); return forceVideo();
@ -1395,16 +1353,18 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) { public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Page.waitForFunction", return mainFrame.waitForFunction(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class));
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
} }
@Override @Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) { public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
final LoadState loadState = state == null ? LoadState.LOAD : state;
withTitle("Wait for load state \"" + loadState.toString().toLowerCase() + "\"", () -> {
withWaitLogging("Page.waitForLoadState", logger -> { withWaitLogging("Page.waitForLoadState", logger -> {
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger); mainFrame.waitForLoadStateImpl(loadState, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
return null; return null;
}); });
});
} }
@Override @Override
@ -1471,7 +1431,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) { public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code); return waitForRequest(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
} }
@Override @Override
@ -1516,7 +1476,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) { public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code); return waitForResponse(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
} }
@Override @Override
@ -1549,8 +1509,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) { public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Page.waitForSelector", return mainFrame.waitForSelector(selector, convertType(options, Frame.WaitForSelectorOptions.class));
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
} }
@Override @Override
@ -1564,12 +1523,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override @Override
public void waitForTimeout(double timeout) { public void waitForTimeout(double timeout) {
withLogging("Page.waitForTimeout", () -> mainFrame.waitForTimeoutImpl(timeout)); mainFrame.waitForTimeout(timeout);
} }
@Override @Override
public void waitForURL(String url, WaitForURLOptions options) { public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(browserContext.baseUrl, url), options); waitForURL(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), options);
} }
@Override @Override

View File

@ -52,7 +52,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env); Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
PlaywrightImpl result = connection.initializePlaywright(); PlaywrightImpl result = connection.initializePlaywright();
result.driverProcess = p; result.driverProcess = p;
result.initSharedSelectors(null);
return result; return result;
} catch (IOException e) { } catch (IOException e) {
throw new PlaywrightException("Failed to launch driver", e); throw new PlaywrightException("Failed to launch driver", e);
@ -62,9 +61,8 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium; private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox; private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit; private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest; private final APIRequestImpl apiRequest;
private SharedSelectors sharedSelectors; protected SelectorsImpl selectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
@ -72,26 +70,20 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString()); firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString()); webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString()); chromium.playwright = this;
firefox.playwright = this;
webkit.playwright = this;
selectors = new SelectorsImpl();
apiRequest = new APIRequestImpl(this); apiRequest = new APIRequestImpl(this);
} }
void initSharedSelectors(PlaywrightImpl parent) { public LocalUtils localUtils() {
assert sharedSelectors == null; return connection.localUtils;
if (parent == null) {
sharedSelectors = new SharedSelectors();
} else {
sharedSelectors = parent.sharedSelectors;
}
sharedSelectors.addChannel(selectors);
}
void unregisterSelectors() {
sharedSelectors.removeChannel(selectors);
} }
public JsonArray deviceDescriptors() { public JsonArray deviceDescriptors() {
return connection.localUtils.deviceDescriptors(); return localUtils().deviceDescriptors();
} }
@Override @Override
@ -116,7 +108,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
@Override @Override
public Selectors selectors() { public Selectors selectors() {
return sharedSelectors; return selectors;
} }
@Override @Override

View File

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
// This file is generated by generate_java_rpc.js, do not edit manually.
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import java.util.List; import java.util.List;
@ -105,6 +103,7 @@ class ExpectedTextValue {
class FrameExpectOptions { class FrameExpectOptions {
Object expressionArg; Object expressionArg;
List<ExpectedTextValue> expectedText; List<ExpectedTextValue> expectedText;
String selector;
Double expectedNumber; Double expectedNumber;
SerializedArgument expectedValue; SerializedArgument expectedValue;
Boolean useInnerText; Boolean useInnerText;

View File

@ -26,8 +26,4 @@ public class RemoteBrowser extends ChannelOwner {
BrowserImpl browser() { BrowserImpl browser() {
return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString()); return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
} }
SelectorsImpl selectors() {
return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
}
} }

View File

@ -68,7 +68,7 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override @Override
public Map<String, String> allHeaders() { public Map<String, String> allHeaders() {
return withLogging("Request.allHeaders", () -> getRawHeaders().headers()); return getRawHeaders().headers();
} }
@Override @Override
@ -97,12 +97,12 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override @Override
public List<HttpHeader> headersArray() { public List<HttpHeader> headersArray() {
return withLogging("Request.headersArray", () -> getRawHeaders().headersArray()); return getRawHeaders().headersArray();
} }
@Override @Override
public String headerValue(String name) { public String headerValue(String name) {
return withLogging("Request.headerValue", () -> getRawHeaders().get(name)); return getRawHeaders().get(name);
} }
@Override @Override
@ -152,25 +152,21 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override @Override
public ResponseImpl response() { public ResponseImpl response() {
return withLogging("Request.response", () -> {
JsonObject result = sendMessage("response").getAsJsonObject(); JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) { if (!result.has("response")) {
return null; return null;
} }
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString()); return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
});
} }
@Override @Override
public Sizes sizes() { public Sizes sizes() {
return withLogging("Request.sizes", () -> {
ResponseImpl response = response(); ResponseImpl response = response();
if (response == null) { if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request"); throw new PlaywrightException("Unable to fetch sizes for failed request");
} }
JsonObject json = response.sendMessage("sizes").getAsJsonObject(); JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class); return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
});
} }
@Override @Override
@ -197,10 +193,8 @@ public class RequestImpl extends ChannelOwner implements Request {
if (rawHeaders != null) { if (rawHeaders != null) {
return rawHeaders; return rawHeaders;
} }
JsonArray rawHeadersJson = withLogging("Request.allHeaders", () -> {
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject(); JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
return result.getAsJsonArray("headers"); JsonArray rawHeadersJson = result.getAsJsonArray("headers");
});
// The field may have been initialized in a nested call but it is ok. // The field may have been initialized in a nested call but it is ok.
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class))); rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));

View File

@ -47,15 +47,13 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override @Override
public Map<String, String> allHeaders() { public Map<String, String> allHeaders() {
return withLogging("Response.allHeaders", () -> getRawHeaders().headers()); return getRawHeaders().headers();
} }
@Override @Override
public byte[] body() { public byte[] body() {
return withLogging("Response.body", () -> {
JsonObject json = sendMessage("body").getAsJsonObject(); JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString()); return Base64.getDecoder().decode(json.get("binary").getAsString());
});
} }
@Override @Override
@ -95,7 +93,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override @Override
public List<HttpHeader> headersArray() { public List<HttpHeader> headersArray() {
return withLogging("Response.headersArray", () -> getRawHeaders().headersArray()); return getRawHeaders().headersArray();
} }
@Override @Override
@ -120,24 +118,20 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override @Override
public SecurityDetails securityDetails() { public SecurityDetails securityDetails() {
return withLogging("Response.securityDetails", () -> {
JsonObject json = sendMessage("securityDetails").getAsJsonObject(); JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) { if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class); return gson().fromJson(json.get("value"), SecurityDetails.class);
} }
return null; return null;
});
} }
@Override @Override
public ServerAddr serverAddr() { public ServerAddr serverAddr() {
return withLogging("Response.serverAddr", () -> {
JsonObject json = sendMessage("serverAddr").getAsJsonObject(); JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) { if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class); return gson().fromJson(json.get("value"), ServerAddr.class);
} }
return null; return null;
});
} }
@Override @Override

View File

@ -44,12 +44,9 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override @Override
public void abort(String errorCode) { public void abort(String errorCode) {
startHandling(); startHandling();
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode); params.addProperty("errorCode", errorCode);
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("abort", params); sendMessageAsync("abort", params);
});
} }
boolean isHandled() { boolean isHandled() {
@ -64,7 +61,7 @@ public class RouteImpl extends ChannelOwner implements Route {
void resume(ResumeOptions options, boolean isFallback) { void resume(ResumeOptions options, boolean isFallback) {
startHandling(); startHandling();
applyOverrides(convertType(options, FallbackOptions.class)); applyOverrides(convertType(options, FallbackOptions.class));
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume(), isFallback)); resumeImpl(request().fallbackOverridesForResume(), isFallback);
} }
@Override @Override
@ -135,7 +132,6 @@ public class RouteImpl extends ChannelOwner implements Route {
params.addProperty("postData", base64); params.addProperty("postData", base64);
} }
} }
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
params.addProperty("isFallback", isFallback); params.addProperty("isFallback", isFallback);
sendMessageAsync("continue", params); sendMessageAsync("continue", params);
} }
@ -153,7 +149,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override @Override
public void fulfill(FulfillOptions options) { public void fulfill(FulfillOptions options) {
startHandling(); startHandling();
withLogging("Route.fulfill", () -> fulfillImpl(options)); fulfillImpl(options);
} }
private void fulfillImpl(FulfillOptions options) { private void fulfillImpl(FulfillOptions options) {
@ -231,7 +227,6 @@ public class RouteImpl extends ChannelOwner implements Route {
if (fetchResponseUid != null) { if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid); params.addProperty("fetchResponseUid", fetchResponseUid);
} }
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("fulfill", params); sendMessageAsync("fulfill", params);
} }

View File

@ -23,6 +23,7 @@ import com.microsoft.playwright.Route;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -99,27 +100,7 @@ class Router {
} }
JsonObject interceptionPatterns() { JsonObject interceptionPatterns() {
JsonArray jsonPatterns = new JsonArray(); List<UrlMatcher> matchers = routes.stream().map(r -> r.matcher).collect(Collectors.toList());
for (RouteInfo route : routes) { return Utils.interceptionPatterns(matchers);
JsonObject jsonPattern = new JsonObject();
Object urlFilter = route.matcher.rawSource;
if (urlFilter instanceof String) {
jsonPattern.addProperty("glob", (String) urlFilter);
} else if (urlFilter instanceof Pattern) {
Pattern pattern = (Pattern) urlFilter;
jsonPattern.addProperty("regexSource", pattern.pattern());
jsonPattern.addProperty("regexFlags", toJsRegexFlags(pattern));
} else {
// Match all requests.
jsonPattern.addProperty("glob", "**/*");
jsonPatterns = new JsonArray();
jsonPatterns.add(jsonPattern);
break;
}
jsonPatterns.add(jsonPattern);
}
JsonObject result = new JsonObject();
result.add("patterns", jsonPatterns);
return result;
} }
} }

View File

@ -17,28 +17,72 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors; import com.microsoft.playwright.Selectors;
import static com.microsoft.playwright.impl.Serialization.gson; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
class SelectorsImpl extends ChannelOwner { import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { import static java.nio.charset.StandardCharsets.UTF_8;
super(parent, type, guid, initializer);
}
void register(String name, String script, Selectors.RegisterOptions options) { public class SelectorsImpl extends LoggingSupport implements Selectors {
if (options == null) { protected final List<BrowserContextImpl> contextsForSelectors = new ArrayList<>();
options = new Selectors.RegisterOptions(); protected final List<JsonObject> selectorEngines = new ArrayList<>();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
params.addProperty("source", script);
sendMessage("register", params);
}
void setTestIdAttributeName(String name) { String testIdAttributeName = "data-testid";
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
for (BrowserContextImpl context : contextsForSelectors) {
try {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", name); params.addProperty("testIdAttributeName", attributeName);
sendMessageAsync("setTestIdAttributeName", params); context.sendMessageAsync("setTestIdAttributeName", params);
} catch (PlaywrightException e) {
}
}
}
@Override
public void register(String name, String script, RegisterOptions options) {
registerImpl(name, script, options);
}
@Override
public void register(String name, Path path, RegisterOptions options) {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
}
private void registerImpl(String name, String script, RegisterOptions options) {
if (selectorEngines.stream().anyMatch(engine -> name.equals(engine.get("name").getAsString()))) {
throw new PlaywrightException("selectors.register: \"" + name + "\" selector engine has been already registered");
}
JsonObject engine = new JsonObject();
engine.addProperty("name", name);
engine.addProperty("source", script);
if (options != null && options.contentScript != null) {
engine.addProperty("contentScript", options.contentScript);
}
for (BrowserContextImpl context : contextsForSelectors) {
JsonObject params = new JsonObject();
params.add("selectorEngine", engine);
context.sendMessage("registerSelectorEngine", params, NO_TIMEOUT);
}
selectorEngines.add(engine);
} }
} }

View File

@ -47,6 +47,7 @@ class Serialization {
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe()) .registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>()) .registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>()) .registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
.registerTypeAdapter(Contrast.class, new ToLowerCaseAndDashSerializer<Contrast>())
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>()) .registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>()) .registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
.registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer<HttpCredentialsSend>()) .registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer<HttpCredentialsSend>())
@ -65,6 +66,7 @@ class Serialization {
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer()) .registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer()) .registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer()) .registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
.registerTypeAdapter(LocatorImpl.class, new LocatorImplSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create(); .registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
static Gson gson() { static Gson gson() {
@ -425,6 +427,7 @@ class Serialization {
private static boolean isSupported(Type type) { private static boolean isSupported(Type type) {
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) || return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<Contrast>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName()); new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
@ -488,6 +491,13 @@ class Serialization {
} }
} }
private static class LocatorImplSerializer implements JsonSerializer<LocatorImpl> {
@Override
public JsonElement serialize(LocatorImpl src, Type typeOfSrc, JsonSerializationContext context) {
return src.toProtocol();
}
}
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> { private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
@Override @Override
public void write(JsonWriter out, SameSiteAttribute value) throws IOException { public void write(JsonWriter out, SameSiteAttribute value) throws IOException {

View File

@ -1,95 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SharedSelectors extends LoggingSupport implements Selectors {
private final List<SelectorsImpl> channels = new ArrayList<>();
private final List<Registration> registrations = new ArrayList<>();
String testIdAttributeName = "data-testid";
private static class Registration {
final String name;
final String script;
final RegisterOptions options;
Registration(String name, String script, RegisterOptions options) {
this.name = name;
this.script = script;
this.options = options;
}
}
@Override
public void register(String name, String script, RegisterOptions options) {
withLogging("Selectors.register", () -> registerImpl(name, script, options));
}
@Override
public void register(String name, Path path, RegisterOptions options) {
withLogging("Selectors.register", () -> {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
});
}
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
channels.forEach(channel -> channel.setTestIdAttributeName(testIdAttributeName));
}
void addChannel(SelectorsImpl channel) {
registrations.forEach(r -> {
try {
channel.register(r.name, r.script, r.options);
} catch (PlaywrightException e) {
// This should not fail except for connection closure, but just in case we catch.
}
channel.setTestIdAttributeName(testIdAttributeName);
});
channels.add(channel);
}
void removeChannel(SelectorsImpl channel) {
channels.remove(channel);
}
private void registerImpl(String name, String script, RegisterOptions options) {
channels.forEach(impl -> impl.register(name, script, options));
registrations.add(new Registration(name, script, options));
}
}

View File

@ -50,7 +50,7 @@ public class Stream extends ChannelOwner {
} }
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("size", len); params.addProperty("size", len);
JsonObject json = sendMessage("read", params).getAsJsonObject(); JsonObject json = sendMessage("read", params, NO_TIMEOUT).getAsJsonObject();
String encoded = json.get("binary").getAsString(); String encoded = json.get("binary").getAsString();
if (encoded.isEmpty()) { if (encoded.isEmpty()) {
return -1; return -1;

View File

@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
class TimeoutSettings { class TimeoutSettings {
private static final int DEFAULT_TIMEOUT_MS = 30_000; private static final int DEFAULT_TIMEOUT_MS = 30_000;
private static final int DEFAULT_LAUNCH_TIMEOUT_MS = 180_000;
private final TimeoutSettings parent; private final TimeoutSettings parent;
private Double defaultTimeout ; private Double defaultTimeout ;
@ -80,4 +81,11 @@ class TimeoutSettings {
} }
return new WaitableTimeout<>(timeout(timeout)); return new WaitableTimeout<>(timeout(timeout));
} }
static double launchTimeout(Double timeout) {
if (timeout != null) {
return timeout;
}
return DEFAULT_LAUNCH_TIMEOUT_MS;
}
} }

View File

@ -19,6 +19,8 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.Touchscreen; import com.microsoft.playwright.Touchscreen;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class TouchscreenImpl implements Touchscreen { class TouchscreenImpl implements Touchscreen {
private final PageImpl page; private final PageImpl page;
@ -28,11 +30,9 @@ class TouchscreenImpl implements Touchscreen {
@Override @Override
public void tap(double x, double y) { public void tap(double x, double y) {
page.withLogging("Touchscreen.tap", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("x", x); params.addProperty("x", x);
params.addProperty("y", y); params.addProperty("y", y);
page.sendMessage("touchscreenTap", params); page.sendMessage("touchscreenTap", params, NO_TIMEOUT);
});
} }
} }

View File

@ -45,7 +45,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
// Not interested in artifacts. // Not interested in artifacts.
if (path == null) { if (path == null) {
params.addProperty("mode", "discard"); params.addProperty("mode", "discard");
sendMessage("tracingStopChunk", params); sendMessage("tracingStopChunk", params, NO_TIMEOUT);
if (stacksId != null) { if (stacksId != null) {
connection.localUtils().traceDiscarded(stacksId); connection.localUtils().traceDiscarded(stacksId);
} }
@ -55,14 +55,14 @@ class TracingImpl extends ChannelOwner implements Tracing {
boolean isLocal = !connection.isRemote; boolean isLocal = !connection.isRemote;
if (isLocal) { if (isLocal) {
params.addProperty("mode", "entries"); params.addProperty("mode", "entries");
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject(); JsonObject json = sendMessage("tracingStopChunk", params, NO_TIMEOUT).getAsJsonObject();
JsonArray entries = json.getAsJsonArray("entries"); JsonArray entries = json.getAsJsonArray("entries");
connection.localUtils.zip(path, entries, stacksId, false, includeSources); connection.localUtils.zip(path, entries, stacksId, false, includeSources);
return; return;
} }
params.addProperty("mode", "archive"); params.addProperty("mode", "archive");
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject(); JsonObject json = sendMessage("tracingStopChunk", params, NO_TIMEOUT).getAsJsonObject();
// The artifact may be missing if the browser closed while stopping tracing. // The artifact may be missing if the browser closed while stopping tracing.
if (!json.has("artifact")) { if (!json.has("artifact")) {
if (stacksId != null) { if (stacksId != null) {
@ -85,6 +85,25 @@ class TracingImpl extends ChannelOwner implements Tracing {
tracingStartChunk(options.name, options.title); tracingStartChunk(options.name, options.title);
} }
@Override
public void group(String name, GroupOptions options) {
groupImpl(name, options);
}
private void groupImpl(String name, GroupOptions options) {
if (options == null) {
options = new GroupOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
sendMessage("tracingGroup", params, NO_TIMEOUT);
}
@Override
public void groupEnd() {
sendMessage("tracingGroupEnd");
}
private void tracingStartChunk(String name, String title) { private void tracingStartChunk(String name, String title) {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
if (name != null) { if (name != null) {
@ -93,7 +112,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
if (title != null) { if (title != null) {
params.addProperty("title", title); params.addProperty("title", title);
} }
JsonObject result = sendMessage("tracingStartChunk", params).getAsJsonObject(); JsonObject result = sendMessage("tracingStartChunk", params, NO_TIMEOUT).getAsJsonObject();
startCollectingStacks(result.get("traceName").getAsString()); startCollectingStacks(result.get("traceName").getAsString());
} }
@ -115,7 +134,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
if (includeSources) { if (includeSources) {
params.addProperty("sources", true); params.addProperty("sources", true);
} }
sendMessage("tracingStart", params); sendMessage("tracingStart", params, NO_TIMEOUT);
tracingStartChunk(options.name, options.title); tracingStartChunk(options.name, options.title);
} }

View File

@ -18,32 +18,25 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.PlaywrightException;
import java.net.MalformedURLException; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Objects;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.globToRegex; import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class UrlMatcher { class UrlMatcher {
final Object rawSource; public final String glob;
private final Predicate<String> predicate; public final Pattern pattern;
public final Predicate<String> predicate;
private static Predicate<String> toPredicate(Pattern pattern) { static UrlMatcher forOneOf(URL baseUrl, Object object, LocalUtils localUtils, boolean isWebSocketUrl) {
return s -> pattern.matcher(s).find();
}
static UrlMatcher any() {
return new UrlMatcher((Object) null, null);
}
static UrlMatcher forOneOf(URL baseUrl, Object object) {
if (object == null) { if (object == null) {
return UrlMatcher.any(); return new UrlMatcher(null, null, null);
} }
if (object instanceof String) { if (object instanceof String) {
return new UrlMatcher(baseUrl, (String) object); return UrlMatcher.forGlob(baseUrl, (String) object, localUtils, isWebSocketUrl);
} }
if (object instanceof Pattern) { if (object instanceof Pattern) {
return new UrlMatcher((Pattern) object); return new UrlMatcher((Pattern) object);
@ -55,34 +48,48 @@ class UrlMatcher {
} }
static String resolveUrl(URL baseUrl, String spec) { static String resolveUrl(URL baseUrl, String spec) {
return resolveUrl(baseUrl.toString(), spec);
}
private static String resolveUrl(String baseUrl, String spec) {
if (baseUrl == null) { if (baseUrl == null) {
return spec; return spec;
} }
try { try {
return new URL(baseUrl, spec).toString(); // Join using URI instead of URL since URL doesn't handle ws(s) protocols.
} catch (MalformedURLException e) { return new URI(baseUrl).resolve(spec).toString();
} catch (URISyntaxException e) {
return spec; return spec;
} }
} }
UrlMatcher(URL base, String url) { static UrlMatcher forGlob(URL baseURL, String glob, LocalUtils localUtils, boolean isWebSocketUrl) {
this(url, toPredicate(Pattern.compile(globToRegex(resolveUrl(base, url)))).or(s -> url == null || url.equals(s))); Pattern pattern = localUtils.globToRegex(glob, baseURL != null ? baseURL.toString() : null, isWebSocketUrl);
return new UrlMatcher(glob, pattern, null);
} }
UrlMatcher(Pattern pattern) { UrlMatcher(Pattern pattern) {
this(pattern, toPredicate(pattern)); this(null, pattern, null);
}
UrlMatcher(Predicate<String> predicate) {
this(predicate, predicate);
} }
private UrlMatcher(Object rawSource, Predicate<String> predicate) { UrlMatcher(Predicate<String> predicate) {
this.rawSource = rawSource; this(null, null, predicate);
}
private UrlMatcher(String glob, Pattern pattern, Predicate<String> predicate) {
this.glob = glob;
this.pattern = pattern;
this.predicate = predicate; this.predicate = predicate;
} }
boolean test(String value) { boolean test(String value) {
return predicate == null || predicate.test(value); if (pattern != null) {
return pattern.matcher(value).find();
}
if (predicate != null) {
return predicate.test(value);
}
return true;
} }
@Override @Override
@ -90,25 +97,40 @@ class UrlMatcher {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
UrlMatcher that = (UrlMatcher) o; UrlMatcher that = (UrlMatcher) o;
if (rawSource instanceof Pattern && that.rawSource instanceof Pattern) { if (pattern != null) {
Pattern a = (Pattern) rawSource; return that.pattern != null && pattern.pattern().equals(that.pattern.pattern()) && pattern.flags() == that.pattern.flags();
Pattern b = (Pattern) that.rawSource;
return a.pattern().equals(b.pattern()) && a.flags() == b.flags();
} }
return Objects.equals(rawSource, that.rawSource); if (predicate != null) {
return predicate.equals(that.predicate);
}
if (glob != null) {
return glob.equals(that.glob);
}
return that.pattern == null && that.predicate == null && that.glob == null;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(rawSource); if (pattern != null) {
return pattern.hashCode();
}
if (predicate != null) {
return predicate.hashCode();
}
if (glob != null) {
return glob.hashCode();
}
return 0;
} }
@Override @Override
public String toString() { public String toString() {
if (rawSource == null) if (glob != null)
return "<any>"; return String.format("<glob pattern=\"%s\">", glob);
if (rawSource instanceof Predicate) if (pattern != null)
return "matching predicate"; return String.format("<regex pattern=\"%s\" flags=\"%s\">", pattern.pattern(), toJsRegexFlags(pattern));
return rawSource.toString(); if (this.predicate != null)
return "<predicate>";
return "<true>";
} }
} }

View File

@ -17,7 +17,6 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException; import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.ClientCertificate; import com.microsoft.playwright.options.ClientCertificate;
@ -32,11 +31,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.toJsonArray; import static com.microsoft.playwright.impl.Serialization.toJsonArray;
import static java.nio.file.Files.readAllBytes; import static java.nio.file.Files.readAllBytes;
@ -91,79 +90,6 @@ public class Utils {
return convertType(f, (Class<T>) f.getClass()); return convertType(f, (Class<T>) f.getClass());
} }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
static Set<Character> escapeGlobChars = new HashSet<>(Arrays.asList('$', '^', '+', '.', '*', '(', ')', '|', '\\', '?', '{', '}', '[', ']'));
static String globToRegex(String glob) {
StringBuilder tokens = new StringBuilder();
tokens.append('^');
boolean inGroup = false;
for (int i = 0; i < glob.length(); ++i) {
char c = glob.charAt(i);
if (c == '\\' && i + 1 < glob.length()) {
char nextChar = glob.charAt(++i);
if (escapeGlobChars.contains(nextChar)) {
tokens.append('\\');
}
tokens.append(nextChar);
continue;
}
if (c == '*') {
boolean beforeDeep = i < 1 || glob.charAt(i - 1) == '/';
int starCount = 1;
while (i + 1 < glob.length() && glob.charAt(i + 1) == '*') {
starCount++;
i++;
}
boolean afterDeep = i + 1 >= glob.length() || glob.charAt(i + 1) == '/';
boolean isDeep = starCount > 1 && beforeDeep && afterDeep;
if (isDeep) {
tokens.append("((?:[^/]*(?:\\/|$))*)");
i++;
} else {
tokens.append("([^/]*)");
}
continue;
}
switch (c) {
case '?':
tokens.append('.');
break;
case '[':
tokens.append('[');
break;
case ']':
tokens.append(']');
break;
case '{':
inGroup = true;
tokens.append('(');
break;
case '}':
inGroup = false;
tokens.append(')');
break;
case ',':
if (inGroup) {
tokens.append('|');
break;
}
tokens.append("\\").append(c);
break;
default:
if (escapeGlobChars.contains(c)) {
tokens.append('\\');
}
tokens.append(c);
break;
}
}
tokens.append('$');
return tokens.toString();
}
static String mimeType(Path path) { static String mimeType(Path path) {
String mimeType; String mimeType;
try { try {
@ -275,7 +201,7 @@ public class Utils {
items.add(item); items.add(item);
} }
tempFilesParams.add("items", items); tempFilesParams.add("items", items);
return context.sendMessage("createTempFiles", tempFilesParams).getAsJsonObject(); return context.sendMessage("createTempFiles", tempFilesParams, NO_TIMEOUT).getAsJsonObject();
} }
static void checkFilePayloadSize(FilePayload[] files) { static void checkFilePayloadSize(FilePayload[] files) {
@ -424,20 +350,17 @@ public class Utils {
JsonObject jsonCert = new JsonObject(); JsonObject jsonCert = new JsonObject();
jsonCert.addProperty("origin", cert.origin); jsonCert.addProperty("origin", cert.origin);
try { try {
if (cert.certPath != null) { String certBase64 = base64Buffer(cert.cert, cert.certPath);
byte[] bytes = readAllBytes(cert.certPath); if (certBase64 != null) {
String base64 = Base64.getEncoder().encodeToString(bytes); jsonCert.addProperty("cert", certBase64);
jsonCert.addProperty("cert", base64);
} }
if (cert.keyPath != null) { String keyBase64 = base64Buffer(cert.key, cert.keyPath);
byte[] bytes = readAllBytes(cert.keyPath); if (keyBase64 != null) {
String base64 = Base64.getEncoder().encodeToString(bytes); jsonCert.addProperty("key", keyBase64);
jsonCert.addProperty("key", base64);
} }
if (cert.pfxPath != null) { String pfxBase64 = base64Buffer(cert.pfx, cert.pfxPath);
byte[] bytes = readAllBytes(cert.pfxPath); if (pfxBase64 != null) {
String base64 = Base64.getEncoder().encodeToString(bytes); jsonCert.addProperty("pfx", pfxBase64);
params.addProperty("pfx", base64);
} }
} catch (IOException e) { } catch (IOException e) {
throw new PlaywrightException("Failed to read from file", e); throw new PlaywrightException("Failed to read from file", e);
@ -450,4 +373,37 @@ public class Utils {
params.remove("clientCertificates"); params.remove("clientCertificates");
params.add("clientCertificates", clientCertificates); params.add("clientCertificates", clientCertificates);
} }
private static String base64Buffer(byte[] bytes, Path path) throws IOException {
if (path != null) {
bytes = readAllBytes(path);
}
if (bytes == null) {
return null;
}
return Base64.getEncoder().encodeToString(bytes);
}
static JsonObject interceptionPatterns(List<UrlMatcher> matchers) {
JsonArray jsonPatterns = new JsonArray();
for (UrlMatcher matcher: matchers) {
JsonObject jsonPattern = new JsonObject();
if (matcher.glob != null) {
jsonPattern.addProperty("glob", matcher.glob);
} else if (matcher.pattern != null) {
jsonPattern.addProperty("regexSource", matcher.pattern.pattern());
jsonPattern.addProperty("regexFlags", toJsRegexFlags(matcher.pattern));
} else {
// Match all requests.
jsonPattern.addProperty("glob", "**/*");
jsonPatterns = new JsonArray();
jsonPatterns.add(jsonPattern);
break;
}
jsonPatterns.add(jsonPattern);
}
JsonObject result = new JsonObject();
result.add("patterns", jsonPatterns);
return result;
}
} }

View File

@ -30,7 +30,6 @@ class VideoImpl implements Video {
VideoImpl(PageImpl page) { VideoImpl(PageImpl page) {
this.page = page; this.page = page;
BrowserImpl browser = page.context().browser();
} }
void setArtifact(ArtifactImpl artifact) { void setArtifact(ArtifactImpl artifact) {
@ -44,17 +43,14 @@ class VideoImpl implements Video {
@Override @Override
public void delete() { public void delete() {
page.withLogging("Video.delete", () -> {
try { try {
waitForArtifact().delete(); waitForArtifact().delete();
} catch (PlaywrightException e) { } catch (PlaywrightException e) {
} }
});
} }
@Override @Override
public Path path() { public Path path() {
return page.withLogging("Video.path", () -> {
if (page.connection.isRemote) { if (page.connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy."); throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
} }
@ -63,12 +59,10 @@ class VideoImpl implements Video {
} catch (PlaywrightException e) { } catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e); throw new PlaywrightException("Page did not produce any video frames", e);
} }
});
} }
@Override @Override
public void saveAs(Path path) { public void saveAs(Path path) {
page.withLogging("Video.saveAs", () -> {
if (!page.isClosed()) { if (!page.isClosed()) {
throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs"); throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs");
} }
@ -77,6 +71,5 @@ class VideoImpl implements Video {
} catch (PlaywrightException e) { } catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e); throw new PlaywrightException("Page did not produce any video frames", e);
} }
});
} }
} }

View File

@ -38,7 +38,6 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
@Override @Override
public T get() { public T get() {
return channel.withLogging(apiName, () -> {
{ {
JsonObject info = new JsonObject(); JsonObject info = new JsonObject();
info.addProperty("phase", "before"); info.addProperty("phase", "before");
@ -54,7 +53,6 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
} finally { } finally {
sendWaitForEventInfo(info); sendWaitForEventInfo(info);
} }
});
} }
@Override @Override

View File

@ -20,6 +20,7 @@ import java.util.Collection;
class WaitableRace<T> implements Waitable<T> { class WaitableRace<T> implements Waitable<T> {
private final Collection<Waitable<T>> waitables; private final Collection<Waitable<T>> waitables;
private Waitable<T> firstReady;
WaitableRace(Collection<Waitable<T>> waitables) { WaitableRace(Collection<Waitable<T>> waitables) {
this.waitables = waitables; this.waitables = waitables;
@ -27,8 +28,12 @@ class WaitableRace<T> implements Waitable<T> {
@Override @Override
public boolean isDone() { public boolean isDone() {
for (Waitable w : waitables) { if (firstReady != null) {
return true;
}
for (Waitable<T> w : waitables) {
if (w.isDone()) { if (w.isDone()) {
firstReady = w;
return true; return true;
} }
} }
@ -37,15 +42,12 @@ class WaitableRace<T> implements Waitable<T> {
@Override @Override
public T get() { public T get() {
assert isDone(); try {
return firstReady.get();
} finally {
dispose(); dispose();
for (Waitable<T> w : waitables) {
if (w.isDone()) {
return w.get();
} }
} }
throw new IllegalStateException("At least one element must be ready");
}
@Override @Override
public void dispose() { public void dispose() {

View File

@ -0,0 +1,29 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.WebSocketFrame;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
class WebSocketFrameImpl implements WebSocketFrame {
private byte[] bytes;
private String text;
WebSocketFrameImpl(String payload, boolean isBase64) {
if (isBase64) {
bytes = Base64.getDecoder().decode(payload);
} else {
text = payload;
}
}
@Override
public byte[] binary() {
return bytes;
}
@Override
public String text() {
return text;
}
}

View File

@ -21,9 +21,7 @@ import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.WebSocket; import com.microsoft.playwright.WebSocket;
import com.microsoft.playwright.WebSocketFrame; import com.microsoft.playwright.WebSocketFrame;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -151,28 +149,6 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
return runUntil(code, new WaitableRace<>(waitables)); return runUntil(code, new WaitableRace<>(waitables));
} }
private static class WebSocketFrameImpl implements WebSocketFrame {
private final byte[] bytes;
WebSocketFrameImpl(String payload, boolean isBase64) {
if (isBase64) {
bytes = Base64.getDecoder().decode(payload);
} else {
bytes = payload.getBytes();
}
}
@Override
public byte[] binary() {
return bytes;
}
@Override
public String text() {
return new String(bytes, StandardCharsets.UTF_8);
}
}
@Override @Override
void handleEvent(String event, JsonObject parameters) { void handleEvent(String event, JsonObject parameters) {
switch (event) { switch (event) {

View File

@ -0,0 +1,190 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.WebSocketFrame;
import com.microsoft.playwright.WebSocketRoute;
import java.util.Base64;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
class WebSocketRouteImpl extends ChannelOwner implements WebSocketRoute {
private Consumer<WebSocketFrame> onPageMessage;
private BiConsumer<Integer, String> onPageClose;
private Consumer<WebSocketFrame> onServerMessage;
private BiConsumer<Integer, String> onServerClose;
private boolean connected;
private WebSocketRoute server = new WebSocketRoute() {
@Override
public void close(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("wasClean", true);
sendMessageAsync("closeServer", params);
}
@Override
public WebSocketRoute connectToServer() {
throw new PlaywrightException("connectToServer must be called on the page-side WebSocketRoute");
}
@Override
public void onClose(BiConsumer<Integer, String> handler) {
onServerClose = handler;
}
@Override
public void onMessage(Consumer<WebSocketFrame> handler) {
onServerMessage = handler;
}
@Override
public void send(String message) {
JsonObject params = new JsonObject();
params.addProperty("message", message);
params.addProperty("isBase64", false);
sendMessageAsync("sendToServer", params);
}
@Override
public void send(byte[] message) {
JsonObject params = new JsonObject();
String base64 = Base64.getEncoder().encodeToString(message);
params.addProperty("message", base64);
params.addProperty("isBase64", true);
sendMessageAsync("sendToServer", params);
}
@Override
public String url() {
return initializer.get("url").getAsString();
}
};
WebSocketRouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void close(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("wasClean", true);
sendMessageAsync("closePage", params);
}
@Override
public WebSocketRoute connectToServer() {
if (connected) {
throw new PlaywrightException("Already connected to the server");
}
connected = true;
sendMessageAsync("connect");
return server;
}
@Override
public void onClose(BiConsumer<Integer, String> handler) {
onPageClose = handler;
}
@Override
public void onMessage(Consumer<WebSocketFrame> handler) {
onPageMessage = handler;
}
@Override
public void send(String message) {
JsonObject params = new JsonObject();
params.addProperty("message", message);
params.addProperty("isBase64", false);
sendMessageAsync("sendToPage", params);
}
@Override
public void send(byte[] message) {
JsonObject params = new JsonObject();
String base64 = Base64.getEncoder().encodeToString(message);
params.addProperty("message", base64);
params.addProperty("isBase64", true);
sendMessageAsync("sendToPage", params);
}
@Override
public String url() {
return initializer.get("url").getAsString();
}
void afterHandle() {
if (this.connected) {
return;
}
// Ensure that websocket is "open" and can send messages without an actual server connection.
try {
sendMessageAsync("ensureOpened");
} catch (PlaywrightException e) {
// If this happens after the page has been closed, ignore the error.
}
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("messageFromPage".equals(event)) {
String message = params.get("message").getAsString();
boolean isBase64 = params.get("isBase64").getAsBoolean();
if (onPageMessage != null) {
onPageMessage.accept(new WebSocketFrameImpl(message, isBase64));
} else if (connected) {
JsonObject messageParams = new JsonObject();
messageParams.addProperty("message", message);
messageParams.addProperty("isBase64", isBase64);
sendMessageAsync("sendToServer", messageParams);
}
} else if ("messageFromServer".equals(event)) {
String message = params.get("message").getAsString();
boolean isBase64 = params.get("isBase64").getAsBoolean();
if (onServerMessage != null) {
onServerMessage.accept(new WebSocketFrameImpl(message, isBase64));
} else {
JsonObject messageParams = new JsonObject();
messageParams.addProperty("message", message);
messageParams.addProperty("isBase64", isBase64);
sendMessageAsync("sendToPage", messageParams);
}
} else if ("closePage".equals(event)) {
int code = params.get("code").getAsInt();
String reason = params.get("reason").getAsString();
boolean wasClean = params.get("wasClean").getAsBoolean();
if (onPageClose != null) {
onPageClose.accept(code, reason);
} else {
JsonObject closeParams = new JsonObject();
closeParams.addProperty("code", code);
closeParams.addProperty("reason", reason);
closeParams.addProperty("wasClean", wasClean);
sendMessageAsync("closeServer", closeParams);
}
} else if ("closeServer".equals(event)) {
int code = params.get("code").getAsInt();
String reason = params.get("reason").getAsString();
boolean wasClean = params.get("wasClean").getAsBoolean();
if (onServerClose != null) {
onServerClose.accept(code, reason);
} else {
JsonObject closeParams = new JsonObject();
closeParams.addProperty("code", code);
closeParams.addProperty("reason", reason);
closeParams.addProperty("wasClean", wasClean);
sendMessageAsync("closePage", closeParams);
}
}
}
}

View File

@ -0,0 +1,47 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.WebSocketRoute;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class WebSocketRouter {
private List<RouteInfo> routes = new ArrayList<>();
private static class RouteInfo {
final UrlMatcher matcher;
private final Consumer<WebSocketRoute> handler;
RouteInfo(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
this.matcher = matcher;
this.handler = handler;
}
void handle(WebSocketRouteImpl route) {
handler.accept(route);
route.afterHandle();
}
}
void add(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
routes.add(0, new RouteInfo(matcher, handler));
}
boolean handle(WebSocketRouteImpl route) {
for (RouteInfo routeInfo: routes) {
if (routeInfo.matcher.test(route.url())) {
routeInfo.handle(route);
return true;
}
}
return false;
}
JsonObject interceptionPatterns() {
List<UrlMatcher> matchers = routes.stream().map(r -> r.matcher).collect(Collectors.toList());
return Utils.interceptionPatterns(matchers);
}
}

View File

@ -71,25 +71,21 @@ class WorkerImpl extends ChannelOwner implements Worker {
@Override @Override
public Object evaluate(String pageFunction, Object arg) { public Object evaluate(String pageFunction, Object arg) {
return withLogging("Worker.evaluate", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params); JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value); return deserialize(value);
});
} }
@Override @Override
public JSHandle evaluateHandle(String pageFunction, Object arg) { public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Worker.evaluateHandle", () -> {
JsonObject params = new JsonObject(); JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction); params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg))); params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params); JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString()); return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
});
} }
@Override @Override

View File

@ -42,7 +42,7 @@ class WritableStream extends ChannelOwner {
ByteBuffer buffer = ByteBuffer.wrap(b, off, len); ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
ByteBuffer encoded = Base64.getEncoder().encode(buffer); ByteBuffer encoded = Base64.getEncoder().encode(buffer);
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8)); params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
sendMessage("write", params); sendMessage("write", params, NO_TIMEOUT);
} }
@Override @Override

Some files were not shown because too many files have changed in this diff Show More