1
0
mirror of synced 2026-05-23 03:03:15 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Yury Semikhatsky d36b36e770 cherry-pick(#1552): fix(driver): consider PLAYWRIGHT_NODEJS_PATH from… (#1557) 2024-04-22 15:59:42 -07:00
Yury Semikhatsky 26861a2f1e chore: roll 1.43.0 (#1545) 2024-04-10 09:53:28 -07:00
Yury Semikhatsky 04e77b9c9a chore: set release version to 1.43.0 (#1540) 2024-04-10 09:20:07 -07:00
Yury Semikhatsky ccf4575bc8 chore(1.43): roll 1.43-beta driver (#1539) 2024-04-03 16:18:02 -07:00
191 changed files with 2011 additions and 9464 deletions
+27 -39
View File
@@ -25,12 +25,7 @@ extends:
stages:
- stage: Stage
jobs:
- job: Build
templateContext:
outputs:
- output: pipelineArtifact
path: $(Build.ArtifactStagingDirectory)/esrp-build
artifact: esrp-build
- job: HostJob
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
@@ -52,42 +47,35 @@ extends:
GPG_PRIVATE_KEY_BASE64: $(GPG_PRIVATE_KEY_BASE64) # secret variable has to be mapped to an env variable
displayName: "Import gpg key"
- bash: ./scripts/download_driver.sh
- bash: ./scripts/download_driver_for_all_platforms.sh
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:$(Build.ArtifactStagingDirectory)/esrp-build
- 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
displayName: 'Build and deploy to a local directory'
env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- job: Publish
dependsOn: Build
templateContext:
type: releaseJob
isProduction: true
- bash: |
for file in $(find snapshots -type f); do
echo "processing: $file"
if [[ $file =~ \.(md5|sha1|sha256)$ ]]; then
continue
fi
sha256sum "$file" | cut -f1 -d \ > "$file.sha256"
done
displayName: 'Create .sha256 files'
- task: EsrpRelease@4
inputs:
- input: pipelineArtifact
artifactName: esrp-build
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'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution'
contenttype: 'Maven'
# Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder'
folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
displayName: 'ESRP Release to Maven'
ConnectedServiceName: 'Playwright-ESRP'
Intent: 'PackageDistribution'
ContentType: 'Maven'
ContentSource: 'Folder'
FolderLocation: './local-build'
WaitForReleaseCompletion: true
Owners: 'yurys@microsoft.com'
Approvers: 'maxschmitt@microsoft.com'
ServiceEndpointUrl: 'https://api.esrp.microsoft.com'
MainPublisher: 'Playwright'
DomainTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47'
displayName: 'ESRP Release to Maven'
-28
View File
@@ -1,28 +0,0 @@
// 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"
}
+1 -18
View File
@@ -3,25 +3,8 @@ updates:
- package-ecosystem: "maven"
directory: "/" # Location of the pom.xml file
schedule:
interval: "monthly"
interval: "weekly"
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:
- dependency-type: "direct" # Optional: Only update direct dependencies
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
actions:
patterns:
- "*"
+12 -11
View File
@@ -3,28 +3,29 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
is_release:
required: true
type: boolean
description: "Is this a release image?"
jobs:
publish-canary-docker:
name: publish to DockerHub
runs-on: ubuntu-22.04
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
- uses: azure/docker-login@v1
with:
client-id: ${{ secrets.AZURE_DOCKER_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_DOCKER_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_DOCKER_SUBSCRIPTION_ID }}
- name: Login to ACR via OIDC
run: az acr login --name playwright
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: actions/checkout@v4
- 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')
+15 -24
View File
@@ -8,8 +8,6 @@ on:
branches:
- main
- release-*
env:
PW_MAX_RETRIES: 3
jobs:
dev:
timeout-minutes: 30
@@ -20,19 +18,18 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v2
with:
distribution: zulu
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver.sh
run: scripts/download_driver_for_all_platforms.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: 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
env:
@@ -65,34 +62,29 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v2
with:
distribution: zulu
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver.sh
run: scripts/download_driver_for_all_platforms.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: Install MS Edge
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
shell: bash
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml
- 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
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
Java_21:
Java_17:
timeout-minutes: 30
strategy:
fail-fast: false
@@ -100,19 +92,18 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: adopt
java-version: 21
java-version: 17
- name: Download drivers
shell: bash
run: scripts/download_driver.sh
run: scripts/download_driver_for_all_platforms.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: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
+3 -3
View File
@@ -13,15 +13,15 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Cache Maven packages
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
run: scripts/download_driver.sh
run: scripts/download_driver_for_all_platforms.sh
- name: Intall Playwright
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
+3 -3
View File
@@ -11,7 +11,7 @@ on:
paths:
- .github/workflows/test_docker.yml
- '**/Dockerfile*'
- scripts/DRIVER_VERSION
- scripts/CLI_VERSION
- '**/pom.xml'
branches:
- main
@@ -24,9 +24,9 @@ jobs:
strategy:
fail-fast: false
matrix:
flavor: [jammy, noble]
flavor: [focal, jammy]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Build Docker image
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Test
+3 -9
View File
@@ -9,19 +9,13 @@ on:
jobs:
trigger:
name: "trigger"
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.PLAYWRIGHT_APP_ID }}
private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }}
repositories: playwright-browsers
- run: |
curl -X POST --fail \
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: ${{ steps.app-token.outputs.token }}
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
+3 -6
View File
@@ -19,15 +19,12 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Download drivers
run: scripts/download_driver.sh
run: scripts/download_driver_for_all_platforms.sh
- name: Regenerate APIs
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
run: scripts/update_readme.sh
- name: Verify API is up to date
+14 -6
View File
@@ -20,12 +20,14 @@ git clone https://github.com/microsoft/playwright-java
cd playwright-java
```
2. Run the following script to download Playwright driver for all platforms into `driver-bundle/src/main/resources/driver/` directory (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
2. Run the following script to download playwright-cli binaries for all platforms into `driver-bundle/src/main/resources/driver/` directory (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
```bash
scripts/download_driver.sh
scripts/download_driver_for_all_platforms.sh
```
Names of published driver archives can be found at https://github.com/microsoft/playwright-cli/actions
### Building and running the tests with Maven
```bash
@@ -39,19 +41,25 @@ BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes
### Generating API
Public Java API is generated from api.json which is produced by `print-api-json` command of playwright CLI. To regenerate Java interfaces for the current driver run the following commands:
Public Java API is generated from api.json which is produced by `playwright-cli print-api-json`. To regenerate
Java interfaces for the current driver run the following commands:
```bash
./scripts/download_driver.sh
./scripts/download_driver_for_all_platforms.sh
./scripts/generate_api.sh
```
#### Updating driver version
Versions of published driver archives can be found in [publish canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) and [publish release](https://github.com/microsoft/playwright/actions/workflows/publish_release_driver.yml) actions logs. To update the driver to a particular version run the following command:
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
```bash
scripts/roll_driver.sh [version]
cat > scripts/CLI_VERSION
<paste new version>
^D
./scripts/download_driver_for_all_platforms.sh -f
./scripts/generate_api.sh
./scripts/update_readme.sh
```
### Code Style
+146 -14
View File
@@ -2,7 +2,8 @@
[![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)
[![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
[![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 Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack)
#### [Website](https://playwright.dev/java/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
@@ -10,19 +11,59 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->136.0.7103.25<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->137.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->124.0.6367.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->124.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
## Documentation
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.
[https://playwright.dev/java/docs/intro](https://playwright.dev/java/docs/intro)
* [Usage](#usage)
- [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)
## API Reference
## Usage
[https://playwright.dev/java/docs/api/class-playwright](https://playwright.dev/java/docs/api/class-playwright)
Playwright requires **Java 8** or newer.
## Example
#### Add Maven dependency
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add 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.
@@ -54,9 +95,100 @@ public class PageScreenshot {
}
```
## Other languages
#### Mobile and geolocation
More comfortable in another programming language? [Playwright](https://playwright.dev) is also available in
- [Node.js (JavaScript / TypeScript)](https://playwright.dev/docs/intro),
- [Python](https://playwright.dev/python/docs/intro).
- [.NET](https://playwright.dev/dotnet/docs/intro),
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
```java
import com.microsoft.playwright.options.*;
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-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.
+3 -2
View File
@@ -2,10 +2,11 @@
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* `./scripts/roll_driver.sh 1.47.0-beta-1726138322000`
* set new driver version in `scripts/CLI_VERSION`
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll
## 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`
<img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75">
-17
View File
@@ -1,17 +0,0 @@
# 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
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.52.0</version>
<version>1.43.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -74,7 +74,7 @@ public class DriverJar extends Driver {
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
}
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
logMessage("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.52.0</version>
<version>1.43.0</version>
</parent>
<artifactId>driver</artifactId>
+2 -3
View File
@@ -6,17 +6,16 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.52.0</version>
<version>1.43.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.51.0</playwright.version>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>${playwright.version}</version>
<version>1.41.0</version>
</dependency>
</dependencies>
<build>
+1 -14
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.52.0</version>
<version>1.43.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -57,23 +57,10 @@
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</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>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
@@ -41,28 +41,10 @@ public interface APIRequest {
* </ul>
*/
public String baseURL;
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public List<ClientCertificate> clientCertificates;
/**
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
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
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@@ -72,12 +54,6 @@ public interface APIRequest {
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
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.
*/
@@ -124,23 +100,6 @@ public interface APIRequest {
this.baseURL = baseURL;
return this;
}
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public NewContextOptions setClientCertificates(List<ClientCertificate> clientCertificates) {
this.clientCertificates = clientCertificates;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
@@ -148,13 +107,6 @@ public interface APIRequest {
this.extraHTTPHeaders = extraHTTPHeaders;
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
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@@ -177,15 +129,6 @@ public interface APIRequest {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
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.
*/
@@ -43,38 +43,13 @@ import java.nio.file.Path;
* object will have its own isolated cookie storage.
*/
public interface APIRequestContext {
class DisposeOptions {
/**
* The reason to be reported to the operations interrupted by the context disposal.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the context disposal.
*/
public DisposeOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
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
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
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
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
@@ -113,25 +88,13 @@ public interface APIRequestContext {
*
* @since v1.16
*/
default void dispose() {
dispose(null);
}
/**
* All responses returned by {@link com.microsoft.playwright.APIRequestContext#get APIRequestContext.get()} and similar
* methods are stored in the memory, so that you can later call {@link com.microsoft.playwright.APIResponse#body
* APIResponse.body()}.This method discards all its resources, calling any method on disposed {@code APIRequestContext}
* will throw an exception.
*
* @since v1.16
*/
void dispose(DisposeOptions options);
void dispose();
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
* context cookies from the response. The method will automatically follow redirects. JSON objects can be passed directly
* to the request.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
@@ -139,8 +102,8 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <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, by specifiying the {@code multipart} parameter:
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -151,7 +114,7 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -164,11 +127,10 @@ public interface APIRequestContext {
}
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
* context cookies from the response. The method will automatically follow redirects. JSON objects can be passed directly
* to the request.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
@@ -176,8 +138,8 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <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, by specifiying the {@code multipart} parameter:
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -188,7 +150,7 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -200,11 +162,10 @@ public interface APIRequestContext {
APIResponse fetch(String urlOrRequest, RequestOptions params);
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
* context cookies from the response. The method will automatically follow redirects. JSON objects can be passed directly
* to the request.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
@@ -212,8 +173,8 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <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, by specifiying the {@code multipart} parameter:
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -224,7 +185,7 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -237,11 +198,10 @@ public interface APIRequestContext {
}
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
* context cookies from the response. The method will automatically follow redirects. JSON objects can be passed directly
* to the request.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
@@ -249,8 +209,8 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <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, by specifiying the {@code multipart} parameter:
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -261,7 +221,7 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -377,8 +337,7 @@ public interface APIRequestContext {
* }</pre>
*
* <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} parameter:
* multipart/form-data} encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -387,9 +346,9 @@ public interface APIRequestContext {
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.post("https://example.com/api/uploadScript",
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -425,8 +384,7 @@ public interface APIRequestContext {
* }</pre>
*
* <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} parameter:
* multipart/form-data} encoding. You can achieve that with Playwright API like this:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -435,9 +393,9 @@ public interface APIRequestContext {
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.post("https://example.com/api/uploadScript",
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
@@ -43,8 +43,8 @@ public interface APIResponse {
*/
Map<String, String> headers();
/**
* An array with all the response HTTP headers associated with this response. Header names are not lower-cased. Headers
* with multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*
* @since v1.16
*/
@@ -29,15 +29,15 @@ import java.util.regex.Pattern;
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox();
* Browser browser = firefox.launch();
* Page page = browser.newPage();
* page.navigate("https://example.com");
* browser.close();
* }
* }
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox()
* Browser browser = firefox.launch();
* Page page = browser.newPage();
* page.navigate('https://example.com');
* browser.close();
* }
* }
* }
* }</pre>
*/
@@ -97,33 +97,11 @@ public interface Browser extends AutoCloseable {
*/
public Boolean bypassCSP;
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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;
/**
* 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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -185,6 +163,10 @@ public interface Browser extends AutoCloseable {
public List<String> permissions;
/**
* 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;
/**
@@ -314,42 +296,14 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public NewContextOptions setClientCertificates(List<ClientCertificate> clientCertificates) {
this.clientCertificates = clientCertificates;
return this;
}
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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) {
this.colorScheme = Optional.ofNullable(colorScheme);
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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -457,12 +411,20 @@ public interface Browser extends AutoCloseable {
}
/**
* 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) {
return setProxy(new Proxy(server));
}
/**
* 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) {
this.proxy = proxy;
@@ -665,33 +627,11 @@ public interface Browser extends AutoCloseable {
*/
public Boolean bypassCSP;
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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;
/**
* 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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -753,6 +693,10 @@ public interface Browser extends AutoCloseable {
public List<String> permissions;
/**
* 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;
/**
@@ -882,42 +826,14 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public NewPageOptions setClientCertificates(List<ClientCertificate> clientCertificates) {
this.clientCertificates = clientCertificates;
return this;
}
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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) {
this.colorScheme = Optional.ofNullable(colorScheme);
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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -1025,12 +941,20 @@ public interface Browser extends AutoCloseable {
}
/**
* 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) {
return setProxy(new Proxy(server));
}
/**
* 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) {
this.proxy = proxy;
@@ -1255,10 +1179,10 @@ public interface Browser extends AutoCloseable {
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** 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.
*
@@ -1274,10 +1198,10 @@ public interface Browser extends AutoCloseable {
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** 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.
*
@@ -1327,7 +1251,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate("https://example.com");
* page.navigate('https://example.com');
*
* // Graceful close up everything
* context.close();
@@ -1354,7 +1278,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate("https://example.com");
* page.navigate('https://example.com');
*
* // Graceful close up everything
* context.close();
@@ -1402,7 +1326,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.navigate("https://www.google.com");
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
@@ -1426,7 +1350,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.navigate("https://www.google.com");
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
@@ -1449,7 +1373,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.navigate("https://www.google.com");
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
@@ -30,9 +30,8 @@ import java.util.regex.Pattern;
* <p> If a page opens another page, e.g. with a {@code window.open} call, the popup will belong to the parent page's browser
* context.
*
* <p> Playwright allows creating isolated non-persistent browser contexts with {@link
* com.microsoft.playwright.Browser#newContext Browser.newContext()} method. Non-persistent browser contexts don't write
* any browsing data to disk.
* <p> Playwright allows creating "incognito" browser contexts with {@link com.microsoft.playwright.Browser#newContext
* Browser.newContext()} method. "Incognito" browser contexts don't write any browsing data to disk.
* <pre>{@code
* // Create a new incognito browser context
* BrowserContext context = browser.newContext();
@@ -45,22 +44,6 @@ import java.util.regex.Pattern;
*/
public interface BrowserContext extends AutoCloseable {
/**
* <strong>NOTE:</strong> Only works with Chromium browser's persistent context.
*
* <p> Emitted when new background page is created in the context.
* <pre>{@code
* context.onBackgroundPage(backgroundPage -> {
* System.out.println(backgroundPage.url());
* });
* }</pre>
*/
void onBackgroundPage(Consumer<Page> handler);
/**
* Removes handler that was previously added with {@link #onBackgroundPage onBackgroundPage(handler)}.
*/
void offBackgroundPage(Consumer<Page> handler);
/**
* Emitted when Browser context gets closed. This might happen because of one of the following:
* <ul>
@@ -128,10 +111,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a
* popup with {@code window.open('http://example.com')}, this event will fire when the network request to
* "http://example.com" is done and its response has started loading in the popup. If you would like to route/listen to
* this network request, use {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()} and {@link
* com.microsoft.playwright.BrowserContext#onRequest BrowserContext.onRequest()} respectively instead of similar methods on
* the {@code Page}.
* "http://example.com" is done and its response has started loading in the popup.
* <pre>{@code
* Page newPage = context.waitForPage(() -> {
* page.getByText("open new page").click();
@@ -280,12 +260,14 @@ public interface BrowserContext extends AutoCloseable {
}
class ExposeBindingOptions {
/**
* @deprecated This option will be removed in the future.
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
* supported. When passing by value, multiple arguments are supported.
*/
public Boolean handle;
/**
* @deprecated This option will be removed in the future.
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
* supported. When passing by value, multiple arguments are supported.
*/
public ExposeBindingOptions setHandle(boolean handle) {
this.handle = handle;
@@ -407,27 +389,12 @@ public interface BrowserContext extends AutoCloseable {
}
}
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
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
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
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
@@ -515,12 +482,6 @@ public interface BrowserContext extends AutoCloseable {
return this;
}
}
/**
* Playwright has ability to mock clock and passage of time.
*
* @since v1.45
*/
Clock clock();
/**
* Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be
* obtained via {@link com.microsoft.playwright.BrowserContext#cookies BrowserContext.cookies()}.
@@ -530,6 +491,9 @@ public interface BrowserContext extends AutoCloseable {
* browserContext.addCookies(Arrays.asList(cookieObject1, cookieObject2));
* }</pre>
*
* @param cookies Adds cookies to the browser context.
*
* <p> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
* @since v1.8
*/
void addCookies(List<Cookie> cookies);
@@ -587,14 +551,6 @@ public interface BrowserContext extends AutoCloseable {
* @since v1.8
*/
void addInitScript(Path script);
/**
* <strong>NOTE:</strong> Background pages are only supported on Chromium-based browsers.
*
* <p> All existing background pages in the context.
*
* @since v1.11
*/
List<Page> backgroundPages();
/**
* Returns the browser instance of the context. If it was launched as a persistent context null gets returned.
*
@@ -716,7 +672,7 @@ public interface BrowserContext extends AutoCloseable {
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit();
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
@@ -734,6 +690,21 @@ public interface BrowserContext extends AutoCloseable {
* }
* }</pre>
*
* <p> An example of passing an element handle:
* <pre>{@code
* context.exposeBinding("clicked", (source, args) -> {
* ElementHandle element = (ElementHandle) args[0];
* System.out.println(element.textContent());
* return null;
* }, new BrowserContext.ExposeBindingOptions().setHandle(true));
* page.setContent("" +
* "<script>\n" +
* " document.addEventListener('click', event => window.clicked(event.target));\n" +
* "</script>\n" +
* "<div>Click me</div>\n" +
* "<div>Or click me</div>\n");
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
* @since v1.8
@@ -763,7 +734,7 @@ public interface BrowserContext extends AutoCloseable {
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit();
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
@@ -781,6 +752,21 @@ public interface BrowserContext extends AutoCloseable {
* }
* }</pre>
*
* <p> An example of passing an element handle:
* <pre>{@code
* context.exposeBinding("clicked", (source, args) -> {
* ElementHandle element = (ElementHandle) args[0];
* System.out.println(element.textContent());
* return null;
* }, new BrowserContext.ExposeBindingOptions().setHandle(true));
* page.setContent("" +
* "<script>\n" +
* " document.addEventListener('click', event => window.clicked(event.target));\n" +
* "</script>\n" +
* "<div>Click me</div>\n" +
* "<div>Or click me</div>\n");
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
* @since v1.8
@@ -812,9 +798,8 @@ public interface BrowserContext extends AutoCloseable {
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit();
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeFunction("sha256", args -> {
* String text = (String) args[0];
* MessageDigest crypto;
@@ -849,28 +834,23 @@ public interface BrowserContext extends AutoCloseable {
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified.
*
* @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:
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* <ul>
* <li> {@code "accelerometer"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "accelerometer"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "midi"}</li>
* <li> {@code "notifications"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* </ul>
* @since v1.8
*/
@@ -881,28 +861,23 @@ public interface BrowserContext extends AutoCloseable {
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified.
*
* @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:
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* <ul>
* <li> {@code "accelerometer"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "midi"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "notifications"}</li>
* <li> {@code "camera"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "accelerometer"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
* <li> {@code "midi"}</li>
* <li> {@code "notifications"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "clipboard-read"}</li>
* <li> {@code "clipboard-write"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* </ul>
* @since v1.8
*/
@@ -951,7 +926,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -992,8 +967,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1007,7 +982,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -1048,8 +1023,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1061,7 +1036,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -1102,8 +1077,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1117,7 +1092,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -1158,8 +1133,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1171,7 +1146,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -1212,8 +1187,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1227,7 +1202,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <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 Workers when using request interception by setting {@code serviceWorkers} to {@code "block"}.
* Service Workers when using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* <p> <strong>Usage</strong>
*
@@ -1268,8 +1243,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1281,7 +1256,7 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code serviceWorkers} to {@code "block"}.
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code
* path} is a relative path, then it is resolved relative to the current working directory.
@@ -1296,94 +1271,13 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> Playwright will not serve requests intercepted by Service Worker from the HAR file. See <a
* href="https://github.com/microsoft/playwright/issues/1090">this</a> issue. We recommend disabling Service Workers when
* using request interception by setting {@code serviceWorkers} to {@code "block"}.
* using request interception by setting {@code Browser.newContext.serviceWorkers} to {@code "block"}.
*
* @param har Path to a <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> file with prerecorded network data. If {@code
* path} is a relative path, then it is resolved relative to the current working directory.
* @since v1.23
*/
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:
* <ul>
@@ -1411,7 +1305,7 @@ public interface BrowserContext extends AutoCloseable {
* com.microsoft.playwright.BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()} take
* priority over {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*
* @param timeout Maximum time in milliseconds. Pass {@code 0} to disable timeout.
* @param timeout Maximum time in milliseconds
* @since v1.8
*/
void setDefaultTimeout(double timeout);
@@ -1450,7 +1344,7 @@ public interface BrowserContext extends AutoCloseable {
*/
void setOffline(boolean offline);
/**
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*
* @since v1.8
*/
@@ -1458,7 +1352,7 @@ public interface BrowserContext extends AutoCloseable {
return storageState(null);
}
/**
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*
* @since v1.8
*/
@@ -172,14 +172,9 @@ public interface BrowserType {
*/
public List<String> args;
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
@@ -226,8 +221,8 @@ public interface BrowserType {
/**
* 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://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
@@ -270,28 +265,18 @@ public interface BrowserType {
}
@Deprecated
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchOptions setChannel(String channel) {
this.channel = channel;
@@ -368,8 +353,8 @@ public interface BrowserType {
/**
* 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://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -461,14 +446,9 @@ public interface BrowserType {
*/
public Boolean bypassCSP;
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public Object channel;
/**
@@ -476,33 +456,11 @@ public interface BrowserType {
*/
public Boolean chromiumSandbox;
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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;
/**
* 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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -564,8 +522,8 @@ public interface BrowserType {
/**
* 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://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
@@ -760,28 +718,18 @@ public interface BrowserType {
}
@Deprecated
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel.
*
* <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>.
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel;
@@ -795,42 +743,14 @@ public interface BrowserType {
return this;
}
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <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}.
*/
public LaunchPersistentContextOptions setClientCertificates(List<ClientCertificate> clientCertificates) {
this.clientCertificates = clientCertificates;
return this;
}
/**
* Emulates <a
* 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"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. 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) {
this.colorScheme = Optional.ofNullable(colorScheme);
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
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -934,8 +854,8 @@ public interface BrowserType {
/**
* 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://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -1212,24 +1132,22 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* <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}.
* @param wsEndpoint A browser websocket endpoint to connect to.
* @since v1.8
*/
default Browser connect(String wsEndpoint) {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* <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}.
* @param wsEndpoint A browser websocket endpoint to connect to.
* @since v1.8
*/
Browser connect(String wsEndpoint, ConnectOptions options);
@@ -1240,11 +1158,6 @@ public interface BrowserType {
*
* <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>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1266,11 +1179,6 @@ public interface BrowserType {
*
* <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>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1365,15 +1273,11 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @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
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
@@ -1385,15 +1289,11 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @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
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
@@ -1,366 +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;
import java.util.Date;
/**
* Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn more
* about <a href="https://playwright.dev/java/docs/clock">clock emulation</a>.
*
* <p> Note that clock is installed for the entire {@code BrowserContext}, so the time in all the pages and iframes is
* controlled by the same clock.
*/
public interface Clock {
class InstallOptions {
/**
* Time to initialize with, current system time by default.
*/
public Object time;
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(long time) {
this.time = time;
return this;
}
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(String time) {
this.time = time;
return this;
}
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(Date time) {
this.time = time;
return this;
}
}
/**
* Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the
* laptop lid for a while and reopening it later, after given time.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().fastForward(1000);
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(long ticks);
/**
* Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the
* laptop lid for a while and reopening it later, after given time.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().fastForward(1000);
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(String ticks);
/**
* Install fake implementations for the following time-related functions:
* <ul>
* <li> {@code Date}</li>
* <li> {@code setTimeout}</li>
* <li> {@code clearTimeout}</li>
* <li> {@code setInterval}</li>
* <li> {@code clearInterval}</li>
* <li> {@code requestAnimationFrame}</li>
* <li> {@code cancelAnimationFrame}</li>
* <li> {@code requestIdleCallback}</li>
* <li> {@code cancelIdleCallback}</li>
* <li> {@code performance}</li>
* </ul>
*
* <p> Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and
* control the behavior of time-dependent functions. See {@link com.microsoft.playwright.Clock#runFor Clock.runFor()} and
* {@link com.microsoft.playwright.Clock#fastForward Clock.fastForward()} for more information.
*
* @since v1.45
*/
default void install() {
install(null);
}
/**
* Install fake implementations for the following time-related functions:
* <ul>
* <li> {@code Date}</li>
* <li> {@code setTimeout}</li>
* <li> {@code clearTimeout}</li>
* <li> {@code setInterval}</li>
* <li> {@code clearInterval}</li>
* <li> {@code requestAnimationFrame}</li>
* <li> {@code cancelAnimationFrame}</li>
* <li> {@code requestIdleCallback}</li>
* <li> {@code cancelIdleCallback}</li>
* <li> {@code performance}</li>
* </ul>
*
* <p> Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and
* control the behavior of time-dependent functions. See {@link com.microsoft.playwright.Clock#runFor Clock.runFor()} and
* {@link com.microsoft.playwright.Clock#fastForward Clock.fastForward()} for more information.
*
* @since v1.45
*/
void install(InstallOptions options);
/**
* Advance the clock, firing all the time-related callbacks.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().runFor(1000);
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(long ticks);
/**
* Advance the clock, firing all the time-related callbacks.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().runFor(1000);
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(String ticks);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</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.
* @since v1.45
*/
void pauseAt(long time);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</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.
* @since v1.45
*/
void pauseAt(String time);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</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.
* @since v1.45
*/
void pauseAt(Date time);
/**
* Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
*
* @since v1.45
*/
void resume();
/**
* 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>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(long time);
/**
* 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>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(String time);
/**
* 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>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(Date time);
/**
* 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>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(long time);
/**
* 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>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(String time);
/**
* 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>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(Date time);
}
@@ -20,7 +20,7 @@ import java.util.*;
/**
* {@code ConsoleMessage} objects are dispatched by page via the {@link com.microsoft.playwright.Page#onConsoleMessage
* Page.onConsoleMessage()} event. For each console message logged in the page there will be corresponding event in the
* Page.onConsoleMessage()} event. For each console messages logged in the page there will be corresponding event in the
* Playwright context.
* <pre>{@code
* // Listen for all console messages and print them to the standard output.
@@ -39,8 +39,8 @@ import java.util.*;
* });
*
* // Deconstruct console.log arguments
* msg.args().get(0).jsonValue(); // hello
* msg.args().get(1).jsonValue(); // 42
* msg.args().get(0).jsonValue() // hello
* msg.args().get(1).jsonValue() // 42
* }</pre>
*/
public interface ConsoleMessage {
@@ -65,7 +65,9 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -96,7 +98,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public CheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -157,12 +161,13 @@ public interface ElementHandle extends JSHandle {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -215,15 +220,16 @@ public interface ElementHandle extends JSHandle {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public ClickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public ClickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -280,12 +286,13 @@ public interface ElementHandle extends JSHandle {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -331,15 +338,16 @@ public interface ElementHandle extends JSHandle {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public DblclickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public DblclickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -387,7 +395,9 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -407,7 +417,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public FillOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -432,12 +444,13 @@ public interface ElementHandle extends JSHandle {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -469,15 +482,16 @@ public interface ElementHandle extends JSHandle {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public HoverOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public HoverOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -544,7 +558,9 @@ public interface ElementHandle extends JSHandle {
*/
public Double delay;
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -563,7 +579,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public PressOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -599,9 +617,7 @@ public interface ElementHandle extends JSHandle {
public ScreenshotCaret caret;
/**
* 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. 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.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
*/
public List<Locator> mask;
/**
@@ -675,9 +691,7 @@ 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
* {@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.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask;
@@ -781,7 +795,9 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -801,7 +817,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -858,7 +876,9 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -889,7 +909,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -932,7 +954,9 @@ public interface ElementHandle extends JSHandle {
}
class SetInputFilesOptions {
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -944,7 +968,9 @@ public interface ElementHandle extends JSHandle {
public Double timeout;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetInputFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -969,12 +995,13 @@ public interface ElementHandle extends JSHandle {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1006,15 +1033,16 @@ public interface ElementHandle extends JSHandle {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public TapOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TapOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1061,7 +1089,9 @@ public interface ElementHandle extends JSHandle {
*/
public Double delay;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1080,7 +1110,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TypeOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1104,7 +1136,9 @@ public interface ElementHandle extends JSHandle {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1135,7 +1169,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public UncheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1288,6 +1324,7 @@ public interface ElementHandle extends JSHandle {
* force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
@@ -1310,6 +1347,7 @@ public interface ElementHandle extends JSHandle {
* force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
@@ -1375,6 +1413,8 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to double click in the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the
* first click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -1397,6 +1437,8 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to double click in the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the
* first click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -1653,6 +1695,7 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to hover over the center of the element, or the specified
* {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -1673,6 +1716,7 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to hover over the center of the element, or the specified
* {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -1776,7 +1820,7 @@ public interface ElementHandle extends JSHandle {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -1807,7 +1851,7 @@ public interface ElementHandle extends JSHandle {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -1874,8 +1918,6 @@ public interface ElementHandle extends JSHandle {
* <p> Throws when {@code elementHandle} does not point to an element <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected">connected</a> to a Document or a ShadowRoot.
*
* <p> See <a href="https://playwright.dev/java/docs/input#scrolling">scrolling</a> for alternative ways to scroll.
*
* @since v1.8
*/
default void scrollIntoViewIfNeeded() {
@@ -1890,8 +1932,6 @@ public interface ElementHandle extends JSHandle {
* <p> Throws when {@code elementHandle} does not point to an element <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected">connected</a> to a Document or a ShadowRoot.
*
* <p> See <a href="https://playwright.dev/java/docs/input#scrolling">scrolling</a> for alternative ways to scroll.
*
* @since v1.8
*/
void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options);
@@ -2288,6 +2328,7 @@ public interface ElementHandle extends JSHandle {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
@@ -2309,6 +2350,7 @@ public interface ElementHandle extends JSHandle {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
@@ -2321,8 +2363,7 @@ public interface ElementHandle extends JSHandle {
void setChecked(boolean checked, SetCheckedOptions options);
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2337,8 +2378,7 @@ public interface ElementHandle extends JSHandle {
}
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2351,8 +2391,7 @@ public interface ElementHandle extends JSHandle {
void setInputFiles(Path files, SetInputFilesOptions options);
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2367,8 +2406,7 @@ public interface ElementHandle extends JSHandle {
}
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2381,8 +2419,7 @@ public interface ElementHandle extends JSHandle {
void setInputFiles(Path[] files, SetInputFilesOptions options);
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2397,8 +2434,7 @@ public interface ElementHandle extends JSHandle {
}
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2411,8 +2447,7 @@ public interface ElementHandle extends JSHandle {
void setInputFiles(FilePayload files, SetInputFilesOptions options);
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2427,8 +2462,7 @@ public interface ElementHandle extends JSHandle {
}
/**
* Sets the value of the file input to these file paths or files. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files. For inputs with
* a {@code [webkitdirectory]} attribute, only a single directory path is supported.
* they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* <p> This method expects {@code ElementHandle} to point to an <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input">input element</a>. However, if the element is
@@ -2447,6 +2481,7 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#touchscreen Page.touchscreen()} to tap the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -2469,6 +2504,7 @@ public interface ElementHandle extends JSHandle {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#touchscreen Page.touchscreen()} to tap the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> If the element is detached from the DOM at any moment during the action, this method throws.
@@ -2516,6 +2552,7 @@ public interface ElementHandle extends JSHandle {
* force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
@@ -2538,6 +2575,7 @@ public interface ElementHandle extends JSHandle {
* force} option is set.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
@@ -30,7 +30,9 @@ import java.nio.file.Path;
public interface FileChooser {
class SetFilesOptions {
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -42,7 +44,9 @@ public interface FileChooser {
public Double timeout;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -72,7 +72,7 @@ public interface Frame {
*/
public Path path;
/**
* Script type. Use 'module' in order to load a JavaScript ES6 module. See <a
* Script type. Use 'module' in order to load a Javascript ES6 module. See <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script">script</a> for more details.
*/
public String type;
@@ -97,7 +97,7 @@ public interface Frame {
return this;
}
/**
* Script type. Use 'module' in order to load a JavaScript ES6 module. See <a
* Script type. Use 'module' in order to load a Javascript ES6 module. See <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script">script</a> for more details.
*/
public AddScriptTagOptions setType(String type) {
@@ -157,7 +157,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -193,7 +195,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public CheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -262,12 +266,13 @@ public interface Frame {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -290,8 +295,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public Boolean trial;
@@ -326,15 +330,16 @@ public interface Frame {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public ClickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public ClickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -376,8 +381,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public ClickOptions setTrial(boolean trial) {
this.trial = trial;
@@ -400,12 +404,13 @@ public interface Frame {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -428,8 +433,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public Boolean trial;
@@ -457,15 +461,16 @@ public interface Frame {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public DblclickOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public DblclickOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -507,8 +512,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public DblclickOptions setTrial(boolean trial) {
this.trial = trial;
@@ -555,7 +559,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -596,7 +602,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public DragAndDropOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -683,7 +691,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -708,7 +718,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public FillOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1103,12 +1115,13 @@ public interface Frame {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1131,8 +1144,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public Boolean trial;
@@ -1146,15 +1158,16 @@ public interface Frame {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public HoverOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public HoverOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1196,8 +1209,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public HoverOptions setTrial(boolean trial) {
this.trial = trial;
@@ -1595,7 +1607,9 @@ public interface Frame {
*/
public Double delay;
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1619,7 +1633,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option will default to {@code true} in the future.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public PressOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1667,7 +1683,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1692,7 +1710,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SelectOptionOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1724,7 +1744,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1760,7 +1782,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetCheckedOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1860,7 +1884,9 @@ public interface Frame {
}
class SetInputFilesOptions {
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1877,7 +1903,9 @@ public interface Frame {
public Double timeout;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetInputFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -1910,12 +1938,13 @@ public interface Frame {
public Boolean force;
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public List<KeyboardModifier> modifiers;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -1938,8 +1967,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public Boolean trial;
@@ -1953,15 +1981,16 @@ public interface Frame {
}
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
* modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
* and Linux and to "Meta" on macOS.
* modifiers back. If not specified, currently pressed modifiers are used.
*/
public TapOptions setModifiers(List<KeyboardModifier> modifiers) {
this.modifiers = modifiers;
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TapOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -2003,8 +2032,7 @@ public interface Frame {
/**
* When set, this method only performs the <a href="https://playwright.dev/java/docs/actionability">actionability</a>
* checks and skips the action. Defaults to {@code false}. Useful to wait until the element is ready for the action without
* performing it. 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.
* performing it.
*/
public TapOptions setTrial(boolean trial) {
this.trial = trial;
@@ -2050,7 +2078,9 @@ public interface Frame {
*/
public Double delay;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -2074,7 +2104,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public TypeOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -2106,7 +2138,9 @@ public interface Frame {
*/
public Boolean force;
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
@@ -2142,7 +2176,9 @@ public interface Frame {
return this;
}
/**
* @deprecated This option has no effect.
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public UncheckOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
@@ -2487,6 +2523,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
@@ -2509,6 +2546,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked. If not, this method throws.</li>
* </ol>
*
@@ -2579,8 +2617,9 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to double click in the center of the element, or the
* specified {@code position}. if the first click of the {@code dblclick()} triggers a navigation event, this method will
* throw.</li>
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the
* first click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -2602,8 +2641,9 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to double click in the center of the element, or the
* specified {@code position}. if the first click of the {@code dblclick()} triggers a navigation event, this method will
* throw.</li>
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set. Note that if the
* first click of the {@code dblclick()} triggers a navigation event, this method will throw.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -3499,19 +3539,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3541,19 +3581,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3581,19 +3621,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3623,19 +3663,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3783,6 +3823,7 @@ public interface Frame {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to hover over the center of the element, or the specified
* {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -3803,6 +3844,7 @@ public interface Frame {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to hover over the center of the element, or the specified
* {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -4040,8 +4082,7 @@ public interface Frame {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -4070,8 +4111,7 @@ public interface Frame {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -4518,6 +4558,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
@@ -4541,6 +4582,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now checked or unchecked. If not, this method throws.</li>
* </ol>
*
@@ -4701,6 +4743,7 @@ public interface Frame {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#touchscreen Page.touchscreen()} to tap the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -4723,6 +4766,7 @@ public interface Frame {
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#touchscreen Page.touchscreen()} to tap the center of the element, or the
* specified {@code position}.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* </ol>
*
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
@@ -4788,6 +4832,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
@@ -4810,6 +4855,7 @@ public interface Frame {
* unless {@code force} option is set. If the element is detached during the checks, the whole action is retried.</li>
* <li> Scroll the element into view if needed.</li>
* <li> Use {@link com.microsoft.playwright.Page#mouse Page.mouse()} to click in the center of the element.</li>
* <li> Wait for initiated navigations to either succeed or fail, unless {@code noWaitAfter} option is set.</li>
* <li> Ensure that the element is now unchecked. If not, this method throws.</li>
* </ol>
*
@@ -4943,9 +4989,6 @@ public interface Frame {
* <p> This returns when the frame reaches a required load state, {@code load} by default. The navigation must have been
* committed when this method is called. If current document has already reached the required state, resolves immediately.
*
* <p> <strong>NOTE:</strong> Most of the time, this method is not needed because Playwright <a
* href="https://playwright.dev/java/docs/actionability">auto-waits before every action</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* frame.click("button"); // Click triggers navigation.
@@ -4971,9 +5014,6 @@ public interface Frame {
* <p> This returns when the frame reaches a required load state, {@code load} by default. The navigation must have been
* committed when this method is called. If current document has already reached the required state, resolves immediately.
*
* <p> <strong>NOTE:</strong> Most of the time, this method is not needed because Playwright <a
* href="https://playwright.dev/java/docs/actionability">auto-waits before every action</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* frame.click("button"); // Click triggers navigation.
@@ -4991,9 +5031,6 @@ public interface Frame {
* <p> This returns when the frame reaches a required load state, {@code load} by default. The navigation must have been
* committed when this method is called. If current document has already reached the required state, resolves immediately.
*
* <p> <strong>NOTE:</strong> Most of the time, this method is not needed because Playwright <a
* href="https://playwright.dev/java/docs/actionability">auto-waits before every action</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* frame.click("button"); // Click triggers navigation.
@@ -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
* {@code iframe} and locate elements in that iframe. FrameLocator can be created with either {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()}, {@link com.microsoft.playwright.Page#frameLocator
* Page.frameLocator()} or {@link com.microsoft.playwright.Locator#frameLocator Locator.frameLocator()} method.
* com.microsoft.playwright.Page#frameLocator Page.frameLocator()} or {@link com.microsoft.playwright.Locator#frameLocator
* Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.locator("#my-frame").contentFrame().getByText("Submit");
* Locator locator = page.frameLocator("#my-frame").getByText("Submit");
* locator.click();
* }</pre>
*
@@ -35,10 +35,10 @@ import java.util.regex.Pattern;
* a given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* page.locator(".result-frame").contentFrame().getByRole(AriaRole.BUTTON).click();
* page.frame_locator(".result-frame").getByRole(AriaRole.BUTTON).click();
*
* // Works because we explicitly tell locator to pick the first frame:
* page.locator(".result-frame").first().contentFrame().getByRole(AriaRole.BUTTON).click();
* page.frame_locator(".result-frame").first().getByRole(AriaRole.BUTTON).click();
* }</pre>
*
* <p> <strong>Converting Locator to FrameLocator</strong>
@@ -383,8 +383,7 @@ public interface FrameLocator {
}
}
/**
* @deprecated Use {@link com.microsoft.playwright.Locator#first Locator.first()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* Returns locator to the first matching frame.
*
* @since v1.17
*/
@@ -734,19 +733,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -776,19 +775,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -816,19 +815,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -858,19 +857,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world");
* page.getByText("world")
*
* // Matches first <div>
* page.getByText("Hello world");
* page.getByText("Hello world")
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"));
* page.getByText(Pattern.compile("Hello"))
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> <strong>Details</strong>
@@ -954,8 +953,7 @@ public interface FrameLocator {
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* @deprecated Use {@link com.microsoft.playwright.Locator#last Locator.last()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* Returns locator to the last matching frame.
*
* @since v1.17
*/
@@ -1005,8 +1003,7 @@ public interface FrameLocator {
*/
Locator locator(Locator selectorOrLocator, LocatorOptions options);
/**
* @deprecated Use {@link com.microsoft.playwright.Locator#nth Locator.nth()} followed by {@link
* com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()} instead.
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
*
* @since v1.17
*/
@@ -1021,7 +1018,7 @@ public interface FrameLocator {
*
* <p> <strong>Usage</strong>
* <pre>{@code
* FrameLocator frameLocator = page.locator("iframe[name=\"embedded\"]").contentFrame();
* FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
* // ...
* Locator locator = frameLocator.owner();
* assertThat(locator).isVisible();
@@ -48,7 +48,10 @@ import com.microsoft.playwright.options.*;
*
* <p> An example to trigger select-all with the keyboard
* <pre>{@code
* page.keyboard().press("ControlOrMeta+A");
* // on Windows and Linux
* page.keyboard().press("Control+A");
* // on macOS
* page.keyboard().press("Meta+A");
* }</pre>
*/
public interface Keyboard {
@@ -94,8 +97,7 @@ public interface Keyboard {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -145,8 +147,7 @@ public interface Keyboard {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -161,7 +162,7 @@ public interface Keyboard {
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* 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.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
@@ -192,8 +193,7 @@ public interface Keyboard {
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
* ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
@@ -208,7 +208,7 @@ public interface Keyboard {
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* 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.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
File diff suppressed because it is too large Load Diff
@@ -163,8 +163,6 @@ public interface Mouse {
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void click(double x, double y) {
@@ -174,8 +172,6 @@ public interface Mouse {
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void click(double x, double y, ClickOptions options);
@@ -184,8 +180,6 @@ public interface Mouse {
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()} and {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void dblclick(double x, double y) {
@@ -196,8 +190,6 @@ public interface Mouse {
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()} and {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void dblclick(double x, double y, DblclickOptions options);
@@ -218,8 +210,6 @@ public interface Mouse {
/**
* Dispatches a {@code mousemove} event.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void move(double x, double y) {
@@ -228,8 +218,6 @@ public interface Mouse {
/**
* Dispatches a {@code mousemove} event.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void move(double x, double y, MoveOptions options);
@@ -248,8 +236,7 @@ public interface Mouse {
*/
void up(UpOptions options);
/**
* Dispatches a {@code wheel} event. This method is usually used to manually scroll the page. See <a
* href="https://playwright.dev/java/docs/input#scrolling">scrolling</a> for alternative ways to scroll.
* Dispatches a {@code wheel} event.
*
* <p> <strong>NOTE:</strong> Wheel events may cause scrolling if they are not handled, and this method does not wait for the scrolling to finish
* before returning.
File diff suppressed because it is too large Load Diff
@@ -99,7 +99,7 @@ public interface Request {
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case-insensitive.
* Returns the value of the header matching the name. The name is case insensitive.
*
* @param name Name of the header.
* @since v1.15
@@ -71,7 +71,7 @@ public interface Response {
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case-insensitive. If multiple headers have the same name
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n}
* separator is used. If no headers are found, {@code null} is returned.
*
@@ -80,7 +80,7 @@ public interface Response {
*/
String headerValue(String name);
/**
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case-insensitive.
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case insensitive.
*
* @param name Name of the header.
* @since v1.15
@@ -147,12 +147,6 @@ public interface Route {
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects.
*/
public Integer maxRedirects;
/**
* Maximum number of times network errors should be retried. Currently only {@code ECONNRESET} error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to {@code 0} - no
* retries.
*/
public Integer maxRetries;
/**
* If set changes the request method (e.g. GET or POST).
*/
@@ -185,15 +179,6 @@ public interface Route {
this.maxRedirects = maxRedirects;
return this;
}
/**
* Maximum number of times network errors should be retried. Currently only {@code ECONNRESET} error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to {@code 0} - no
* retries.
*/
public FetchOptions setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
/**
* If set changes the request method (e.g. GET or POST).
*/
@@ -348,7 +333,7 @@ public interface Route {
*/
void abort(String errorCode);
/**
* Sends route's request to the network with optional overrides.
* Continues route's request with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -363,16 +348,10 @@ public interface Route {
*
* <p> <strong>Details</strong>
*
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <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()}.
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
*
* @since v1.8
*/
@@ -380,7 +359,7 @@ public interface Route {
resume(null);
}
/**
* Sends route's request to the network with optional overrides.
* Continues route's request with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -395,30 +374,21 @@ public interface Route {
*
* <p> <strong>Details</strong>
*
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <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()}.
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
*
* @since v1.8
*/
void resume(ResumeOptions options);
/**
* 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
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
@@ -472,24 +442,18 @@ public interface Route {
* });
* }</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
*/
default void fallback() {
fallback(null);
}
/**
* 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
* When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
@@ -543,9 +507,6 @@ public interface Route {
* });
* }</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
*/
void fallback(FallbackOptions options);
@@ -20,9 +20,6 @@ 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
* 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 {
/**
@@ -31,8 +28,6 @@ public interface Touchscreen {
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} the method will throw if {@code hasTouch} option of the browser
* context is false.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void tap(double x, double y);
@@ -16,7 +16,6 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
/**
@@ -40,8 +39,8 @@ public interface Tracing {
class StartOptions {
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead.
*/
public String name;
@@ -70,8 +69,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
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead.
*/
public StartOptions setName(String name) {
@@ -116,8 +115,8 @@ public interface Tracing {
class StartChunkOptions {
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead.
*/
public String name;
@@ -128,8 +127,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
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* the final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* tracesDir} folder specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead.
*/
public StartChunkOptions setName(String name) {
@@ -144,29 +143,6 @@ public interface Tracing {
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 {
/**
* Export trace into the file with the given path.
@@ -295,56 +271,6 @@ public interface Tracing {
* @since v1.15
*/
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.
*
@@ -20,10 +20,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* 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}.
* The {@code WebSocket} class represents websocket connections in the page.
*/
public interface WebSocket {
@@ -1,223 +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;
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 communcation 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();
}
@@ -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
* APIResponse} in the tests.
* <pre>{@code
* // ...
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* // ...
* ...
* @Test
* void navigatesToLoginPage() {
* // ...
* APIResponse response = page.request().get("https://playwright.dev");
* ...
* APIResponse response = page.request().get('https://playwright.dev');
* assertThat(response).isOK();
* }
* }
@@ -16,22 +16,20 @@
package com.microsoft.playwright.assertions;
import java.util.*;
import java.util.regex.Pattern;
import com.microsoft.playwright.options.AriaRole;
/**
* The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code
* Locator} state in the tests.
* <pre>{@code
* // ...
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestLocator {
* // ...
* ...
* @Test
* void statusBecomesSubmitted() {
* // ...
* ...
* page.getByRole(AriaRole.BUTTON).click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
@@ -59,37 +57,16 @@ public interface LocatorAssertions {
}
}
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;
/**
* 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}.
*/
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) {
this.checked = checked;
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}.
*/
@@ -238,20 +215,6 @@ public interface LocatorAssertions {
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 {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -290,87 +253,6 @@ public interface LocatorAssertions {
return this;
}
}
class HasAccessibleDescriptionOptions {
/**
* 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 HasAccessibleDescriptionOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasAccessibleDescriptionOptions setTimeout(double timeout) {
this.timeout = timeout;
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 {
/**
* 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 HasAccessibleNameOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasAccessibleNameOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasAttributeOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -468,20 +350,6 @@ public interface LocatorAssertions {
return this;
}
}
class HasRoleOptions {
/**
* 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 HasRoleOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasTextOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -548,20 +416,6 @@ public interface LocatorAssertions {
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
* {@code "error"}:
@@ -829,10 +683,10 @@ public interface LocatorAssertions {
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* assertThat(page.getByTestId("todo-item").first()).isVisible();
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* assertThat(
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
@@ -857,10 +711,10 @@ public interface LocatorAssertions {
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* assertThat(page.getByTestId("todo-item").first()).isVisible();
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* assertThat(
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
@@ -870,98 +724,6 @@ public interface LocatorAssertions {
* @since v1.20
*/
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
* when computing the text content of the element. You can use regular expressions for the value as well.
@@ -1314,186 +1076,6 @@ public interface LocatorAssertions {
* @since v1.20
*/
void containsText(Pattern[] expected, ContainsTextOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleDescription("Save results to disk");
* }</pre>
*
* @param description Expected accessible description.
* @since v1.44
*/
default void hasAccessibleDescription(String description) {
hasAccessibleDescription(description, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleDescription("Save results to disk");
* }</pre>
*
* @param description Expected accessible description.
* @since v1.44
*/
void hasAccessibleDescription(String description, HasAccessibleDescriptionOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleDescription("Save results to disk");
* }</pre>
*
* @param description Expected accessible description.
* @since v1.44
*/
default void hasAccessibleDescription(Pattern description) {
hasAccessibleDescription(description, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleDescription("Save results to disk");
* }</pre>
*
* @param description Expected accessible description.
* @since v1.44
*/
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
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleName("Save to disk");
* }</pre>
*
* @param name Expected accessible name.
* @since v1.44
*/
default void hasAccessibleName(String name) {
hasAccessibleName(name, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleName("Save to disk");
* }</pre>
*
* @param name Expected accessible name.
* @since v1.44
*/
void hasAccessibleName(String name, HasAccessibleNameOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleName("Save to disk");
* }</pre>
*
* @param name Expected accessible name.
* @since v1.44
*/
default void hasAccessibleName(Pattern name) {
hasAccessibleName(name, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasAccessibleName("Save to disk");
* }</pre>
*
* @param name Expected accessible name.
* @since v1.44
*/
void hasAccessibleName(Pattern name, HasAccessibleNameOptions options);
/**
* Ensures the {@code Locator} points to an element with given attribute.
*
@@ -1551,19 +1133,16 @@ public interface LocatorAssertions {
*/
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1575,19 +1154,16 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1597,19 +1173,16 @@ public interface LocatorAssertions {
*/
void hasClass(String expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1621,19 +1194,16 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1643,19 +1213,16 @@ public interface LocatorAssertions {
*/
void hasClass(Pattern expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1667,19 +1234,16 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1689,19 +1253,16 @@ public interface LocatorAssertions {
*/
void hasClass(String[] expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1713,19 +1274,16 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* }</pre>
*
* <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:
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
@@ -1898,42 +1456,6 @@ public interface LocatorAssertions {
* @since v1.20
*/
void hasJSProperty(String name, Object value, HasJSPropertyOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA
* role</a>.
*
* <p> Note that role is matched as a string, disregarding the ARIA role hierarchy. For example, asserting a superclass role
* {@code "checkbox"} on an element with a subclass role {@code "switch"} will fail.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasRole(AriaRole.BUTTON);
* }</pre>
*
* @param role Required aria role.
* @since v1.44
*/
default void hasRole(AriaRole role) {
hasRole(role, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA
* role</a>.
*
* <p> Note that role is matched as a string, disregarding the ARIA role hierarchy. For example, asserting a superclass role
* {@code "checkbox"} on an element with a subclass role {@code "switch"} will fail.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("save-button");
* assertThat(locator).hasRole(AriaRole.BUTTON);
* }</pre>
*
* @param role Required aria role.
* @since v1.44
*/
void hasRole(AriaRole role, HasRoleOptions options);
/**
* Ensures the {@code Locator} points to an element with 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.
@@ -2350,7 +1872,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2368,7 +1890,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2384,7 +1906,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2402,7 +1924,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2410,39 +1932,5 @@ public interface LocatorAssertions {
* @since v1.23
*/
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);
}
@@ -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}
* state in the tests.
* <pre>{@code
* // ...
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* // ...
* ...
* @Test
* void navigatesToLoginPage() {
* // ...
* ...
* page.getByText("Sign in").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
@@ -52,24 +52,11 @@ public interface PageAssertions {
}
}
class HasURLOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression parameter if specified. A provided predicate ignores this flag.
*/
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 parameter if specified. A provided predicate ignores this flag.
*/
public HasURLOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
@@ -30,13 +30,14 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
*
* <p> Consider the following example:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestExample {
* // ...
* ...
* @Test
* void statusBecomesSubmitted() {
* // ...
* ...
* page.locator("#submit-button").click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
@@ -29,7 +29,6 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
@@ -37,7 +36,6 @@ import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
private String disposeReason;
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -50,17 +48,8 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
@Override
public void dispose(DisposeOptions options) {
withLogging("APIRequestContext.dispose", () -> disposeImpl(options));
}
private void disposeImpl(DisposeOptions options) {
if (options == null) {
options = new DisposeOptions();
}
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params);
public void dispose() {
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
}
@Override
@@ -87,9 +76,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
if (disposeReason != null) {
throw new PlaywrightException(disposeReason);
}
if (options == null) {
options = new RequestOptionsImpl();
}
@@ -100,7 +86,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
for (Map.Entry<String, ?> e : options.params.entrySet()) {
queryParams.put(e.getKey(), "" + e.getValue());
}
params.add("params", toNameValueArray(queryParams.entrySet()));
params.add("params", toNameValueArray(queryParams));
}
if (options.method != null) {
params.addProperty("method", options.method);
@@ -120,7 +106,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
}
if (bytes == null) {
params.addProperty("jsonData", jsonDataSerializer.toJson(options.data));
params.addProperty("jsonData", gson().toJson(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
@@ -147,12 +133,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
params.addProperty("maxRedirects", options.maxRedirects);
}
if (options.maxRetries != null) {
if (options.maxRetries < 0) {
throw new PlaywrightException("'maxRetries' must be greater than or equal to '0'");
}
params.addProperty("maxRetries", options.maxRetries);
}
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
@@ -169,9 +149,9 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
return false;
}
private static JsonArray serializeMultipartData(List<? extends Map.Entry<String, Object>> data) {
private static JsonArray serializeMultipartData(Map<String, Object> data) {
JsonArray result = new JsonArray();
for (Map.Entry<String, ?> e : data) {
for (Map.Entry<String, Object> e : data.entrySet()) {
FilePayload filePayload = null;
if (e.getValue() instanceof FilePayload) {
filePayload = (FilePayload) e.getValue();
@@ -17,21 +17,15 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.ClientCertificate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static java.nio.file.Files.readAllBytes;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
@@ -48,8 +42,6 @@ class APIRequestImpl implements APIRequest {
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
options = Utils.clone(options);
}
if (options.storageStatePath != null) {
try {
@@ -65,13 +57,11 @@ class APIRequestImpl implements APIRequest {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
List<ClientCertificate> clientCertificateList = options.clientCertificates;
options.clientCertificates = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
addToProtocol(params, clientCertificateList);
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
return context;
@@ -46,20 +46,22 @@ class APIResponseImpl implements APIResponse {
@Override
public byte[] body() {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
return context.withLogging("APIResponse.body", () -> {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
}
return Base64.getDecoder().decode(json.get("binary").getAsString());
} catch (PlaywrightException e) {
if (isSafeCloseError(e)) {
throw new PlaywrightException("Response has been disposed");
}
throw e;
}
return Base64.getDecoder().decode(json.get("binary").getAsString());
} catch (PlaywrightException e) {
if (isSafeCloseError(e)) {
throw new PlaywrightException("Response has been disposed");
}
throw e;
}
});
}
@Override
@@ -20,7 +20,6 @@ import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
@@ -47,6 +46,7 @@ class AssertionsBase {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
options.isNot = isNot;
expectImpl(expression, options, expected, message);
}
@@ -54,14 +54,13 @@ class AssertionsBase {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
expectOptions.isNot = isNot;
if (isNot) {
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = (result.log == null) ? "" : String.join("\n", result.log);
String log = String.join("\n", result.log);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
@@ -92,17 +91,4 @@ class AssertionsBase {
}
return expected;
}
static Boolean shouldIgnoreCase(Object options) {
if (options == null) {
return null;
}
try {
Field fromField = options.getClass().getDeclaredField("ignoreCase");
Object value = fromField.get(options);
return (Boolean) value;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
}
@@ -36,7 +36,8 @@ import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
@@ -45,12 +46,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
private final ClockImpl clock;
final List<PageImpl> pages = new ArrayList<>();
final List<PageImpl> backgroundPages = new ArrayList<>();
final Router routes = new Router();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closeWasCalled;
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
@@ -84,7 +82,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
enum EventType {
BACKGROUNDPAGE,
CLOSE,
CONSOLE,
DIALOG,
@@ -105,7 +102,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
@@ -133,16 +129,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return null;
}
@Override
public void onBackgroundPage(Consumer<Page> handler) {
listeners.add(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void offBackgroundPage(Consumer<Page> handler) {
listeners.remove(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -233,11 +219,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.RESPONSE, handler);
}
@Override
public ClockImpl clock() {
return clock;
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
@@ -291,7 +272,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new CloseOptions();
}
closeReason = options.reason;
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
@@ -345,11 +325,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
@Override
public List<Page> backgroundPages() {
return new ArrayList<>(backgroundPages);
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
@@ -480,7 +455,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(baseUrl, url, this.connection.localUtils, false), handler, options);
route(new UrlMatcher(baseUrl, url), handler, options);
}
@Override
@@ -502,7 +477,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
recordIntoHar(null, har, options);
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url, this.connection.localUtils, false);
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
@@ -515,28 +490,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
@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) {
withLogging("BrowserContext.routeWebSocket", () -> {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
});
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
JsonObject params = new JsonObject();
if (page != null) {
@@ -623,22 +576,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public String storageState(StorageStateOptions options) {
return withLogging("BrowserContext.storageState", () -> storageStateImpl(options));
}
private String storageStateImpl(StorageStateOptions options) {
if (options == null) {
options = new StorageStateOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonElement json = sendMessage("storageState", params);
String storageState = json.toString();
if (options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
return withLogging("BrowserContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
String storageState = json.toString();
if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
});
}
@Override
@@ -656,7 +601,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(UrlMatcher.forGlob(this.baseUrl, url, this.connection.localUtils, false), handler);
unroute(new UrlMatcher(this.baseUrl, url), handler);
}
@Override
@@ -712,10 +657,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns());
}
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled != Router.HandleResult.NoMatchingHandler) {
@@ -726,12 +667,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
void handleWebSocketRoute(WebSocketRouteImpl route) {
if (!webSocketRoutes.handle(route)) {
route.connectToServer();
}
}
WaitableResult<JsonElement> pause() {
return sendMessageAsync("pause", new JsonObject());
}
@@ -771,9 +706,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = this;
handleRoute(route);
} else if ("webSocketRoute".equals(event)) {
WebSocketRouteImpl route = connection.getExistingObject(params.getAsJsonObject("webSocketRoute").get("guid").getAsString());
handleWebSocketRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page);
@@ -781,10 +713,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
} else if ("backgroundPage".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
backgroundPages.add(page);
listeners.notify(EventType.BACKGROUNDPAGE, page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
@@ -208,7 +208,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
params.addProperty("noDefaultViewport", true);
}
}
addToProtocol(params, options.clientCertificates);
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
@@ -26,12 +26,10 @@ import com.microsoft.playwright.options.HarContentPolicy;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@@ -197,10 +195,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
Path cwd = Paths.get("").toAbsolutePath();
userDataDir = cwd.resolve(userDataDir);
}
params.addProperty("userDataDir", userDataDir.toString());
if (recordHar != null) {
params.add("recordHar", recordHar);
@@ -227,7 +221,6 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
params.addProperty("noDefaultViewport", true);
}
}
addToProtocol(params, options.clientCertificates);
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
@@ -35,10 +35,7 @@ public class CDPSessionImpl extends ChannelOwner implements CDPSession {
super.handleEvent(event, parameters);
if ("event".equals(event)) {
String method = parameters.get("method").getAsString();
JsonObject params = null;
if (parameters.has("params")) {
params = parameters.get("params").getAsJsonObject();
}
JsonObject params = parameters.get("params").getAsJsonObject();
listeners.notify(method, params);
}
}
@@ -35,7 +35,6 @@ class ChannelOwner extends LoggingSupport {
final String guid;
final JsonObject initializer;
private boolean wasCollected;
private boolean isInternalType;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -59,10 +58,6 @@ class ChannelOwner extends LoggingSupport {
}
}
void markAsInternalType() {
isInternalType = true;
}
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
@@ -89,9 +84,6 @@ class ChannelOwner extends LoggingSupport {
@Override
<T> T withLogging(String apiName, Supplier<T> code) {
if (isInternalType) {
apiName = null;
}
String previousApiName = connection.setApiName(apiName);
try {
return super.withLogging(apiName, code);
@@ -100,10 +92,6 @@ class ChannelOwner extends LoggingSupport {
}
}
WaitableResult<JsonElement> sendMessageAsync(String method) {
return sendMessageAsync(method, new JsonObject());
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessageAsync(guid, method, params);
@@ -1,135 +0,0 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Clock;
import java.util.Date;
class ClockImpl implements Clock {
private final ChannelOwner browserContext;
ClockImpl(BrowserContextImpl browserContext) {
this.browserContext = browserContext;
}
private void sendMessageWithLogging(String method, JsonObject params) {
String capitalizedMethod = method.substring(0, 1).toUpperCase() + method.substring(1);
browserContext.withLogging("Clock." + method,
() -> browserContext.sendMessage("clock" + capitalizedMethod, params));
}
@Override
public void fastForward(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
sendMessageWithLogging("fastForward", params);
}
@Override
public void fastForward(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
sendMessageWithLogging("fastForward", params);
}
@Override
public void install(InstallOptions options) {
JsonObject params = new JsonObject();
if (options != null) {
parseTime(options.time, params);
}
sendMessageWithLogging("install", params);
}
@Override
public void runFor(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
sendMessageWithLogging("runFor", params);
}
@Override
public void runFor(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
sendMessageWithLogging("runFor", params);
}
@Override
public void pauseAt(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
sendMessageWithLogging("pauseAt", params);
}
@Override
public void pauseAt(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
sendMessageWithLogging("pauseAt", params);
}
@Override
public void pauseAt(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("pauseAt", params);
}
@Override
public void resume() {
sendMessageWithLogging("resume", new JsonObject());
}
@Override
public void setFixedTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setFixedTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setFixedTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setSystemTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
sendMessageWithLogging("setSystemTime", params);
}
@Override
public void setSystemTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
sendMessageWithLogging("setSystemTime", params);
}
@Override
public void setSystemTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
sendMessageWithLogging("setSystemTime", params);
}
private static void parseTime(Object time, JsonObject params) {
if (time instanceof Long) {
params.addProperty("timeNumber", (Long) time);
} else if (time instanceof Date) {
params.addProperty("timeNumber", ((Date) time).getTime());
} else if (time instanceof String) {
params.addProperty("timeString", (String) time);
}
}
}
@@ -384,9 +384,6 @@ public class Connection {
case "WebSocket":
result = new WebSocketImpl(parent, type, guid, initializer);
break;
case "WebSocketRoute":
result = new WebSocketRouteImpl(parent, type, guid, initializer);
break;
case "Worker":
result = new WorkerImpl(parent, type, guid, initializer);
break;
@@ -21,13 +21,11 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.ElementState;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
@@ -20,93 +20,39 @@ import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.FormData;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
import java.util.Map;
public class FormDataImpl implements FormData {
static class Field implements Map.Entry<String, Object> {
final String name;
final Object value;
private Field(String name, Object value) {
this.name = name;
this.value = value;
}
@Override
public String getKey() {
return name;
}
@Override
public Object getValue() {
return value;
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
}
List<Field> fields = new ArrayList();
@Override
public FormData append(String name, String value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, boolean value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, int value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, Path value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, FilePayload value) {
return appendImpl(name, value);
}
Map<String, Object> fields = new LinkedHashMap<>();
@Override
public FormData set(String name, String value) {
return setImpl(name, value);
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, boolean value) {
return setImpl(name, value);
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, int value) {
return setImpl(name, value);
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, Path value) {
return setImpl(name, value);
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, FilePayload value) {
return setImpl(name, value);
}
private FormData setImpl(String name, Object value) {
fields = fields.stream().filter(f -> !name.equals(f.name)).collect(Collectors.toList());
return appendImpl(name, value);
}
private FormData appendImpl(String name, Object value) {
fields.add(new Field(name, value));
fields.put(name, value);
return this;
}
}
@@ -1031,7 +1031,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url, this.connection.localUtils, false);
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
logger.log("waiting for navigation " + matcher);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
@@ -1078,7 +1078,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(UrlMatcher.forGlob(page.context().baseUrl, url, this.connection.localUtils, false), options);
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
}
@Override
@@ -136,6 +136,6 @@ class FrameLocatorImpl implements FrameLocator {
@Override
public Locator owner() {
return new LocatorImpl(frame, frameSelector, null);
return new LocatorImpl(frame, frameSelector);
}
}
@@ -33,7 +33,6 @@ class JsonPipe extends ChannelOwner implements Transport {
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
private enum EventType { CLOSE }
private boolean isClosed;
private String closeReason = "Browser has been closed";
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -98,19 +97,13 @@ class JsonPipe extends ChannelOwner implements Transport {
incoming.add(params.get("message").getAsJsonObject());
} else if ("closed".equals(event)) {
isClosed = true;
if (params.has("reason")) {
String reason = params.get("reason").getAsString();
if (reason.trim().length() > 0) {
closeReason = reason;
}
}
listeners.notify(EventType.CLOSE, this);
}
}
private void checkIfClosed() {
if (isClosed) {
throw new PlaywrightException(closeReason);
throw new PlaywrightException("Browser has been closed");
}
}
}
@@ -17,7 +17,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.*;
import java.util.function.Consumer;
@@ -47,9 +46,6 @@ class ListenerCollection <EventType> {
}
void add(EventType type, Consumer<?> listener) {
if (listener == null) {
throw new PlaywrightException("Can't add a null listener");
}
List<Consumer<?>> list = listeners.get(type);
if (list == null) {
list = new ArrayList<>();
@@ -21,14 +21,12 @@ import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
public class LocalUtils extends ChannelOwner {
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
JsonArray deviceDescriptors() {
@@ -60,16 +58,4 @@ public class LocalUtils extends ChannelOwner {
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params).getAsJsonObject();
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).getAsJsonObject();
String regex = json.get("regex").getAsString();
return Pattern.compile(regex);
}
}
@@ -18,13 +18,10 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.options.AriaRole;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.serializeArgument;
@@ -39,25 +36,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
super((LocatorImpl) locator, isNot);
}
@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));
}
@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));
}
@Override
public void containsText(String text, ContainsTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
@@ -104,57 +82,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleDescription(String description, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = description;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@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));
}
@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));
}
@Override
public void hasAccessibleName(String name, HasAccessibleNameOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = name;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
@@ -279,13 +206,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
}
@Override
public void hasRole(AriaRole role, HasRoleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = role.toString().toLowerCase();
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(String text, HasTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
@@ -368,42 +288,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
}
@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");
}
@Override
public void isChecked(IsCheckedOptions options) {
if (options == null) {
options = new IsCheckedOptions();
}
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);
boolean unchecked = options != null && options.checked != null && !options.checked;
String expression = unchecked ? "to.be.unchecked" : "to.be.checked";
String message = "Locator expected to be " + (unchecked ? "un" : "") + "checked";
expectTrue(expression, message, convertType(options, FrameExpectOptions.class));
}
@Override
@@ -476,4 +366,17 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
String message = "Locator expected to be " + (attached ? "attached" : "detached");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions);
}
private static Boolean shouldIgnoreCase(Object options) {
if (options == null) {
return null;
}
try {
Field fromField = options.getClass().getDeclaredField("ignoreCase");
Object value = fromField.get(options);
return (Boolean) value;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
}
@@ -21,25 +21,29 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class LocatorImpl implements Locator {
final FrameImpl frame;
final String selector;
LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this(frame, selector, options, null);
LocatorImpl(FrameImpl frame, String frameSelector) {
this(frame, frameSelector, null);
}
private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Boolean visible) {
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
if (options != null) {
if (options.hasText != null) {
@@ -61,9 +65,6 @@ class LocatorImpl implements Locator {
selector += " >> internal:has-not=" + gson().toJson(locator.selector);
}
}
if (visible != null) {
selector += " >> visible=" + visible;
}
this.selector = selector;
}
@@ -118,21 +119,6 @@ class LocatorImpl implements Locator {
return new LocatorImpl(frame, selector + " >> internal:and=" + gson().toJson(other.selector), null);
}
@Override
public String ariaSnapshot(AriaSnapshotOptions options) {
return frame.withLogging("Locator.ariaSnapshot", () -> ariaSnapshotImpl(options));
}
private String ariaSnapshotImpl(AriaSnapshotOptions options) {
if (options == null) {
options = new AriaSnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("ariaSnapshot", params).getAsJsonObject();
return result.get("snapshot").getAsString();
}
@Override
public void blur(BlurOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options));
@@ -251,8 +237,7 @@ class LocatorImpl implements Locator {
@Override
public Locator filter(FilterOptions options) {
Boolean visible = (options == null) ? null : options.visible;
return new LocatorImpl(frame, selector, convertType(options, LocatorOptions.class), visible);
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
}
@Override
@@ -639,20 +624,6 @@ class LocatorImpl implements Locator {
return "Locator@" + selector;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LocatorImpl)) {
return false;
}
LocatorImpl locator = (LocatorImpl) obj;
return frame.equals(locator.frame) && selector.equals(locator.selector);
}
@Override
public int hashCode() {
return frame.hashCode() ^ selector.hashCode();
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
}
@@ -665,6 +636,9 @@ class LocatorImpl implements Locator {
}
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);
@@ -21,7 +21,6 @@ import com.microsoft.playwright.assertions.PageAssertions;
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.Utils.convertType;
@@ -58,7 +57,6 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
url = resolveUrl(actualPage.context().baseUrl, url);
}
expected.string = url;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
}
@@ -48,36 +48,8 @@ public class PageImpl extends ChannelOwner implements Page {
final Waitable<?> waitableClosedOrCrashed;
private ViewportSize viewport;
private final Router routes = new Router();
private final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final Map<Integer, LocatorHandler> locatorHandlers = new HashMap<>();
private static class LocatorHandler {
private final Locator locator;
private final Consumer<Locator> handler;
private Integer times;
LocatorHandler(Locator locator, Consumer<Locator> handler, Integer times) {
this.locator = locator;
this.handler = handler;
this.times = times;
}
boolean call() {
if (shouldRemove()) {
return true;
}
if (times != null) {
--times;
}
handler.accept(locator);
return shouldRemove();
}
private boolean shouldRemove() {
return times != null && times == 0;
}
}
private final Map<Integer, Runnable> locatorHandlers = new HashMap<>();
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
@@ -213,11 +185,6 @@ public class PageImpl extends ChannelOwner implements Page {
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
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)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
@@ -236,7 +203,6 @@ public class PageImpl extends ChannelOwner implements Page {
void didClose() {
isClosed = true;
browserContext.pages.remove(this);
browserContext.backgroundPages.remove(this);
listeners.notify(EventType.CLOSE, this);
}
@@ -437,11 +403,6 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.remove(EventType.WORKER, handler);
}
@Override
public ClockImpl clock() {
return browserContext.clock();
}
@Override
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Page.waitForClose", logger -> waitForCloseImpl(options, code));
@@ -568,58 +529,29 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public void addLocatorHandler(Locator locator, Consumer<Locator> handler, AddLocatorHandlerOptions options) {
public void addLocatorHandler(Locator locator, Runnable handler) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
if (locatorImpl.frame != mainFrame) {
throw new PlaywrightException("Locator must belong to the main frame of this page");
}
if (options == null) {
options = new AddLocatorHandlerOptions();
}
if (options.times != null && options.times == 0) {
return;
}
AddLocatorHandlerOptions finalOptions = options;
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) {
params.addProperty("noWaitAfter", true);
}
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times));
locatorHandlers.put(uid, handler);
});
}
@Override
public void removeLocatorHandler(Locator locator) {
for (Map.Entry<Integer, LocatorHandler> entry: locatorHandlers.entrySet()) {
if (entry.getValue().locator.equals(locator)) {
locatorHandlers.remove(locator);
JsonObject params = new JsonObject();
params.addProperty("uid", entry.getKey());
try {
sendMessage("unregisterLocatorHandler", params);
} catch (PlaywrightException e) {
}
}
}
}
private void onLocatorHandlerTriggered(int uid) {
boolean remove = false;
try {
LocatorHandler handler = locatorHandlers.get(uid);
remove = handler != null && handler.call();
} finally {
if (remove) {
locatorHandlers.remove(uid);
Runnable handler = locatorHandlers.get(uid);
if (handler != null) {
handler.run();
}
} finally {
JsonObject params = new JsonObject();
params.addProperty("uid", uid);
params.addProperty("remove", remove);
sendMessageAsync("resolveLocatorHandlerNoReply", params);
}
}
@@ -785,7 +717,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Frame frameByUrl(String glob) {
return frameFor(UrlMatcher.forGlob(browserContext.baseUrl, glob, this.connection.localUtils, false));
return frameFor(new UrlMatcher(browserContext.baseUrl, glob));
}
@Override
@@ -933,11 +865,6 @@ public class PageImpl extends ChannelOwner implements Page {
return null;
}
@Override
public void requestGC() {
withLogging("Page.requestGC", () -> sendMessage("requestGC"));
}
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
@@ -1105,7 +1032,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), handler, options);
route(new UrlMatcher(browserContext.baseUrl, url), handler, options);
}
@Override
@@ -1127,7 +1054,7 @@ public class PageImpl extends ChannelOwner implements Page {
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url, this.connection.localUtils, false);
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
@@ -1140,28 +1067,6 @@ public class PageImpl extends ChannelOwner implements Page {
});
}
@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) {
withLogging("Page.routeWebSocket", () -> {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
});
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("Page.screenshot", () -> screenshotImpl(options));
@@ -1365,7 +1270,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), handler);
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
}
@Override
@@ -1389,10 +1294,6 @@ public class PageImpl extends ChannelOwner implements Page {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns());
}
@Override
public String url() {
return mainFrame.url();
@@ -1508,7 +1409,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(UrlMatcher.forGlob(browserContext.baseUrl, urlGlob, this.connection.localUtils, false), null, options, code);
return waitForRequest(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
@@ -1553,7 +1454,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(UrlMatcher.forGlob(browserContext.baseUrl, urlGlob, this.connection.localUtils, false), null, options, code);
return waitForResponse(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
@@ -1606,7 +1507,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(UrlMatcher.forGlob(browserContext.baseUrl, url, this.connection.localUtils, false), options);
waitForURL(new UrlMatcher(browserContext.baseUrl, url), options);
}
@Override
@@ -90,12 +90,8 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
sharedSelectors.removeChannel(selectors);
}
public LocalUtils localUtils() {
return connection.localUtils;
}
public JsonArray deviceDescriptors() {
return localUtils().deviceDescriptors();
return connection.localUtils.deviceDescriptors();
}
@Override
@@ -33,12 +33,6 @@ class SerializedValue{
String d;
String u;
String bi;
public static class E {
String m;
String n;
String s;
}
E e;
public static class R {
String p;
String f;
@@ -53,7 +53,6 @@ public class RequestImpl extends ChannelOwner implements Request {
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
if (initializer.has("redirectedFrom")) {
redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
@@ -33,7 +33,6 @@ public class RequestOptionsImpl implements RequestOptions {
Boolean ignoreHTTPSErrors;
Double timeout;
Integer maxRedirects;
Integer maxRetries;
@Override
public RequestOptions setHeader(String name, String value) {
@@ -126,10 +125,4 @@ public class RequestOptionsImpl implements RequestOptions {
this.maxRedirects = maxRedirects;
return this;
}
@Override
public RequestOptions setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
}
@@ -40,7 +40,6 @@ public class ResponseImpl extends ChannelOwner implements Response {
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
@@ -38,7 +38,6 @@ public class RouteImpl extends ChannelOwner implements Route {
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
@@ -48,6 +47,7 @@ public class RouteImpl extends ChannelOwner implements Route {
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("abort", params);
});
}
@@ -135,6 +135,7 @@ public class RouteImpl extends ChannelOwner implements Route {
params.addProperty("postData", base64);
}
}
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
params.addProperty("isFallback", isFallback);
sendMessageAsync("continue", params);
}
@@ -230,6 +231,7 @@ public class RouteImpl extends ChannelOwner implements Route {
if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid);
}
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("fulfill", params);
}
@@ -23,7 +23,6 @@ import com.microsoft.playwright.Route;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -100,7 +99,27 @@ class Router {
}
JsonObject interceptionPatterns() {
List<UrlMatcher> matchers = routes.stream().map(r -> r.matcher).collect(Collectors.toList());
return Utils.interceptionPatterns(matchers);
JsonArray jsonPatterns = new JsonArray();
for (RouteInfo route : routes) {
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;
}
}
@@ -24,7 +24,9 @@ import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.*;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.net.MalformedURLException;
@@ -33,7 +35,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
@@ -47,10 +53,8 @@ class Serialization {
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
.registerTypeAdapter(Contrast.class, new ToLowerCaseAndDashSerializer<Contrast>())
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
.registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer<HttpCredentialsSend>())
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
.registerTypeAdapter(ScreenshotAnimations.class, new ToLowerCaseSerializer<ScreenshotAnimations>())
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
@@ -72,12 +76,6 @@ class Serialization {
return gson;
}
static final Gson jsonDataSerializer = new GsonBuilder().disableHtmlEscaping()
.registerTypeAdapter(Date.class, new DateSerializer())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeSerializer())
.serializeNulls().create();
static SerializedError serializeError(Throwable e) {
SerializedError result = new SerializedError();
result.error = new SerializedError.Error();
@@ -171,14 +169,6 @@ class Serialization {
result.r = new SerializedValue.R();
result.r.p = ((Pattern)value).pattern();
result.r.f = toJsRegexFlags(((Pattern)value));
} else if (value instanceof Exception) {
Exception exception = (Exception) value;
result.e = new SerializedValue.E();
result.e.m = exception.getMessage();
result.e.n = exception.getClass().getSimpleName();
StringWriter sw = new StringWriter();
exception.printStackTrace(new PrintWriter(sw));
result.e.s = sw.toString();
} else {
HashableValue mapKey = new HashableValue(value);
Integer id = valueToId.get(mapKey);
@@ -256,9 +246,6 @@ class Serialization {
return (T)(Date.from(Instant.parse(value.d)));
if (value.r != null)
return (T)(Pattern.compile(value.r.p, fromJsRegexFlags(value.r.f)));
if (value.e != null) {
return (T)new Exception(value.e.s);
}
if (value.v != null) {
switch (value.v) {
case "undefined":
@@ -316,9 +303,6 @@ class Serialization {
if (modifiers.contains(KeyboardModifier.CONTROL)) {
result.add("Control");
}
if (modifiers.contains(KeyboardModifier.CONTROLORMETA)) {
result.add("ControlOrMeta");
}
if (modifiers.contains(KeyboardModifier.META)) {
result.add("Meta");
}
@@ -367,7 +351,7 @@ class Serialization {
throw new PlaywrightException("Value cannot be null");
}
}
return toNameValueArray(map.entrySet());
return toNameValueArray(map);
}
static void addHarUrlFilter(JsonObject options, Object urlFilter) {
@@ -380,9 +364,9 @@ class Serialization {
}
}
static JsonArray toNameValueArray(Iterable<? extends Map.Entry<String, ?>> collection) {
static JsonArray toNameValueArray(Map<String, ?> map) {
JsonArray array = new JsonArray();
for (Map.Entry<String, ?> e : collection) {
for (Map.Entry<String, ?> e : map.entrySet()) {
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
if (e.getValue() instanceof FilePayload) {
@@ -426,7 +410,6 @@ class Serialization {
private static boolean isSupported(Type type) {
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<Contrast>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
@@ -531,13 +514,6 @@ class Serialization {
}
}
private static class OffsetDateTimeSerializer implements JsonSerializer<OffsetDateTime> {
@Override
public JsonElement serialize(OffsetDateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(dateFormat.format(Date.from(src.toInstant())));
}
}
private static class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
@Override
public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
@@ -33,7 +33,6 @@ class TracingImpl extends ChannelOwner implements Tracing {
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
private void stopChunkImpl(Path path) {
@@ -86,25 +85,6 @@ class TracingImpl extends ChannelOwner implements Tracing {
tracingStartChunk(options.name, options.title);
}
@Override
public void group(String name, GroupOptions options) {
withLogging("Tracing.group", () -> 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);
}
@Override
public void groupEnd() {
withLogging("Tracing.groupEnd", () -> sendMessage("tracingGroupEnd"));
}
private void tracingStartChunk(String name, String title) {
JsonObject params = new JsonObject();
if (name != null) {
@@ -18,25 +18,32 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static com.microsoft.playwright.impl.Utils.globToRegex;
class UrlMatcher {
public final String glob;
public final Pattern pattern;
public final Predicate<String> predicate;
final Object rawSource;
private final Predicate<String> predicate;
static UrlMatcher forOneOf(URL baseUrl, Object object, LocalUtils localUtils, boolean isWebSocketUrl) {
private static Predicate<String> toPredicate(Pattern pattern) {
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) {
return new UrlMatcher(null, null, null);
return UrlMatcher.any();
}
if (object instanceof String) {
return UrlMatcher.forGlob(baseUrl, (String) object, localUtils, isWebSocketUrl);
return new UrlMatcher(baseUrl, (String) object);
}
if (object instanceof Pattern) {
return new UrlMatcher((Pattern) object);
@@ -48,48 +55,34 @@ class UrlMatcher {
}
static String resolveUrl(URL baseUrl, String spec) {
return resolveUrl(baseUrl.toString(), spec);
}
private static String resolveUrl(String baseUrl, String spec) {
if (baseUrl == null) {
return spec;
}
try {
// Join using URI instead of URL since URL doesn't handle ws(s) protocols.
return new URI(baseUrl).resolve(spec).toString();
} catch (URISyntaxException e) {
return new URL(baseUrl, spec).toString();
} catch (MalformedURLException e) {
return spec;
}
}
static UrlMatcher forGlob(URL baseURL, String glob, LocalUtils localUtils, boolean isWebSocketUrl) {
Pattern pattern = localUtils.globToRegex(glob, baseURL != null ? baseURL.toString() : null, isWebSocketUrl);
return new UrlMatcher(glob, pattern, null);
UrlMatcher(URL base, String url) {
this(url, toPredicate(Pattern.compile(globToRegex(resolveUrl(base, url)))).or(s -> url == null || url.equals(s)));
}
UrlMatcher(Pattern pattern) {
this(null, pattern, null);
this(pattern, toPredicate(pattern));
}
UrlMatcher(Predicate<String> predicate) {
this(null, null, predicate);
this(predicate, predicate);
}
private UrlMatcher(String glob, Pattern pattern, Predicate<String> predicate) {
this.glob = glob;
this.pattern = pattern;
private UrlMatcher(Object rawSource, Predicate<String> predicate) {
this.rawSource = rawSource;
this.predicate = predicate;
}
boolean test(String value) {
if (pattern != null) {
return pattern.matcher(value).find();
}
if (predicate != null) {
return predicate.test(value);
}
return true;
return predicate == null || predicate.test(value);
}
@Override
@@ -97,40 +90,25 @@ class UrlMatcher {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UrlMatcher that = (UrlMatcher) o;
if (pattern != null) {
return that.pattern != null && pattern.pattern().equals(that.pattern.pattern()) && pattern.flags() == that.pattern.flags();
if (rawSource instanceof Pattern && that.rawSource instanceof Pattern) {
Pattern a = (Pattern) rawSource;
Pattern b = (Pattern) that.rawSource;
return a.pattern().equals(b.pattern()) && a.flags() == b.flags();
}
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;
return Objects.equals(rawSource, that.rawSource);
}
@Override
public int hashCode() {
if (pattern != null) {
return pattern.hashCode();
}
if (predicate != null) {
return predicate.hashCode();
}
if (glob != null) {
return glob.hashCode();
}
return 0;
return Objects.hash(rawSource);
}
@Override
public String toString() {
if (glob != null)
return String.format("<glob pattern=\"%s\">", glob);
if (pattern != null)
return String.format("<regex pattern=\"%s\" flags=\"%s\">", pattern.pattern(), toJsRegexFlags(pattern));
if (this.predicate != null)
return "<predicate>";
return "<true>";
if (rawSource == null)
return "<any>";
if (rawSource instanceof Predicate)
return "matching predicate";
return rawSource.toString();
}
}
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.ClientCertificate;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
@@ -31,12 +30,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
import static java.nio.file.Files.readAllBytes;
public class Utils {
static <F, T> T convertType(F f, Class<T> t) {
@@ -89,6 +87,79 @@ public class Utils {
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) {
String mimeType;
try {
@@ -102,105 +173,40 @@ public class Utils {
return mimeType;
}
static void addFilePathUploadParams(Path[] items, JsonObject params, BrowserContextImpl context) {
List<Path> localPaths = new ArrayList<>();
Path localDirectory = resolvePathsAndDirectoryForInputFiles(items, localPaths);
if (items.length == 0) {
static void addFilePathUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (files.length == 0) {
// FIXME: shouldBeAbleToResetSelectedFilesWithEmptyFileList tesst hangs in Chromium if we pass empty paths list.
params.add("payloads", new JsonArray());
} else if (context.connection.isRemote) {
if (localDirectory != null) {
localPaths = collectFiles(localDirectory);
}
JsonObject json = createTempFiles(context, localDirectory, localPaths);
JsonArray writableStreams = json.getAsJsonArray("writableStreams");
JsonArray jsonStreams = copyLocalToTempFiles(context, localPaths, writableStreams);
if (json.has("rootDir")) {
params.add("directoryStream", json.get("rootDir"));
} else {
params.add("streams", jsonStreams);
}
} else {
if (!localPaths.isEmpty()) {
params.add("localPaths", toJsonArray(localPaths.toArray(new Path[0])));
}
if (localDirectory != null) {
params.addProperty("localDirectory", localDirectory.toString());
}
}
}
private static Path resolvePathsAndDirectoryForInputFiles(Path[] items, List<Path> outLocalPaths) {
Path localDirectory = null;
try {
for (Path item : items) {
if (Files.isDirectory(item)) {
if (localDirectory != null) {
throw new PlaywrightException("Multiple directories are not supported");
}
localDirectory = item.toRealPath();
} else {
outLocalPaths.add(item.toRealPath());
List<WritableStream> streams = new ArrayList<>();
JsonArray jsonStreams = new JsonArray();
for (Path path : files) {
long lastModifiedMs;
try {
lastModifiedMs = Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
throw new PlaywrightException("Cannot read file timestamp: " + path, e);
}
WritableStream temp = context.createTempFile(path.getFileName().toString(), lastModifiedMs);
streams.add(temp);
try (OutputStream out = temp.stream()) {
Files.copy(path, out);
} catch (IOException e) {
throw new PlaywrightException("Failed to copy file to remote server.", e);
}
jsonStreams.add(temp.toProtocolRef());
}
} catch (IOException e) {
throw new PlaywrightException("Cannot get absolute file path", e);
params.add("streams", jsonStreams);
} else {
Path[] absolute = Arrays.stream(files).map(f -> {
try {
return f.toRealPath();
} catch (IOException e) {
throw new PlaywrightException("Cannot get absolute file path", e);
}
}).toArray(Path[]::new);
params.add("localPaths", toJsonArray(absolute));
}
if (!outLocalPaths.isEmpty() && localDirectory != null) {
throw new PlaywrightException("File paths must be all files or a single directory");
}
return localDirectory;
}
private static List<Path> collectFiles(Path localDirectory) {
try {
return Files.walk(localDirectory).filter(e -> Files.isRegularFile(e)).collect(Collectors.toList());
} catch (IOException e) {
throw new PlaywrightException("Failed to traverse directory", e);
}
}
private static JsonArray copyLocalToTempFiles(BrowserContextImpl context, List<Path> localPaths, JsonArray writableStreams) {
JsonArray jsonStreams = new JsonArray();
for (int i = 0; i < localPaths.size(); i++) {
JsonObject jsonStream = writableStreams.get(i).getAsJsonObject();
WritableStream temp = context.connection.getExistingObject(jsonStream.get("guid").getAsString());
try (OutputStream out = temp.stream()) {
Files.copy(localPaths.get(i), out);
} catch (IOException e) {
throw new PlaywrightException("Failed to copy file to remote server.", e);
}
jsonStreams.add(temp.toProtocolRef());
}
return jsonStreams;
}
private static JsonObject createTempFiles(BrowserContextImpl context, Path localDirectory, List<Path> localPaths) {
JsonObject tempFilesParams = new JsonObject();
if (localDirectory != null) {
tempFilesParams.addProperty("rootDirName", localDirectory.getFileName().toString());
}
JsonArray items = new JsonArray();
for (Path path : localPaths) {
long lastModifiedMs;
try {
lastModifiedMs = Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
throw new PlaywrightException("Cannot read file timestamp: " + path, e);
}
Path name;
if (localDirectory == null) {
name = path.getFileName();
} else {
name = localDirectory.relativize(path);
}
JsonObject item = new JsonObject();
item.addProperty("name", name.toString());
item.addProperty("lastModifiedMs", lastModifiedMs);
items.add(item);
}
tempFilesParams.add("items", items);
return context.sendMessage("createTempFiles", tempFilesParams).getAsJsonObject();
}
static void checkFilePayloadSize(FilePayload[] files) {
@@ -339,70 +345,4 @@ public class Utils {
static String addSourceUrlToScript(String source, Path path) {
return source + "\n//# sourceURL=" + path.toString().replace("\n", "");
}
static void addToProtocol(JsonObject params, List<ClientCertificate> clientCertificateList) {
if (clientCertificateList == null) {
return;
}
JsonArray clientCertificates = new JsonArray();
for (ClientCertificate cert: clientCertificateList) {
JsonObject jsonCert = new JsonObject();
jsonCert.addProperty("origin", cert.origin);
try {
String certBase64 = base64Buffer(cert.cert, cert.certPath);
if (certBase64 != null) {
jsonCert.addProperty("cert", certBase64);
}
String keyBase64 = base64Buffer(cert.key, cert.keyPath);
if (keyBase64 != null) {
jsonCert.addProperty("key", keyBase64);
}
String pfxBase64 = base64Buffer(cert.pfx, cert.pfxPath);
if (pfxBase64 != null) {
jsonCert.addProperty("pfx", pfxBase64);
}
} catch (IOException e) {
throw new PlaywrightException("Failed to read from file", e);
}
if (cert.passphrase != null) {
jsonCert.addProperty("passphrase", cert.passphrase);
}
clientCertificates.add(jsonCert);
}
params.remove("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;
}
}
@@ -20,7 +20,6 @@ import java.util.Collection;
class WaitableRace<T> implements Waitable<T> {
private final Collection<Waitable<T>> waitables;
private Waitable<T> firstReady;
WaitableRace(Collection<Waitable<T>> waitables) {
this.waitables = waitables;
@@ -28,12 +27,8 @@ class WaitableRace<T> implements Waitable<T> {
@Override
public boolean isDone() {
if (firstReady != null) {
return true;
}
for (Waitable<T> w : waitables) {
for (Waitable w : waitables) {
if (w.isDone()) {
firstReady = w;
return true;
}
}
@@ -42,11 +37,14 @@ class WaitableRace<T> implements Waitable<T> {
@Override
public T get() {
try {
return firstReady.get();
} finally {
dispose();
assert isDone();
dispose();
for (Waitable<T> w : waitables) {
if (w.isDone()) {
return w.get();
}
}
throw new IllegalStateException("At least one element must be ready");
}
@Override
@@ -1,29 +0,0 @@
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;
}
}
@@ -21,7 +21,9 @@ import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.WebSocket;
import com.microsoft.playwright.WebSocketFrame;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -149,6 +151,28 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
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
void handleEvent(String event, JsonObject parameters) {
switch (event) {
@@ -1,187 +0,0 @@
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);
markAsInternalType();
}
@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.
sendMessageAsync("ensureOpened");
}
@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);
}
}
}
}
@@ -1,47 +0,0 @@
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);
}
}
@@ -16,22 +16,28 @@
package com.microsoft.playwright.impl.junit;
import com.microsoft.playwright.*;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.impl.Utils;
import com.microsoft.playwright.junit.Options;
import org.junit.jupiter.api.extension.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static com.microsoft.playwright.impl.junit.ExtensionUtils.*;
import static com.microsoft.playwright.impl.junit.PageExtension.cleanUpPage;
public class BrowserContextExtension implements ParameterResolver, TestWatcher {
public class BrowserContextExtension implements ParameterResolver, AfterEachCallback {
private static final ThreadLocal<BrowserContext> threadLocalBrowserContext = new ThreadLocal<>();
@Override
public void afterEach(ExtensionContext extensionContext) {
BrowserContext browserContext = threadLocalBrowserContext.get();
threadLocalBrowserContext.remove();
if (browserContext != null) {
browserContext.close();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return !isClassHook(extensionContext) && isParameterSupported(parameterContext, extensionContext, BrowserContext.class);
@@ -45,7 +51,6 @@ public class BrowserContextExtension implements ParameterResolver, TestWatcher {
/**
* Returns the BrowserContext that belongs to the current test. Will be created if it doesn't already exist.
* <strong>NOTE:</strong> this method is subject to change.
*
* @param extensionContext the context in which the current test or container is being executed.
* @return The BrowserContext that belongs to the current test.
*/
@@ -61,97 +66,10 @@ public class BrowserContextExtension implements ParameterResolver, TestWatcher {
Browser browser = BrowserExtension.getOrCreateBrowser(extensionContext);
Browser.NewContextOptions contextOptions = getContextOptions(playwright, options);
browserContext = browser.newContext(contextOptions);
if (shouldRecordTrace(options)) {
Tracing.StartOptions startOptions = new Tracing.StartOptions().setSnapshots(true).setScreenshots(true).setTitle(extensionContext.getDisplayName());
if (System.getenv("PLAYWRIGHT_JAVA_SRC") != null) {
startOptions.setSources(true);
}
browserContext.tracing().start(startOptions);
}
threadLocalBrowserContext.set(browserContext);
return browserContext;
}
@Override
public void testSuccessful(ExtensionContext extensionContext) {
saveTraceWhenOn(extensionContext);
closeBrowserContext();
}
@Override
public void testAborted(ExtensionContext extensionContext, Throwable cause) {
saveTraceWhenOn(extensionContext);
closeBrowserContext();
}
@Override
public void testFailed(ExtensionContext extensionContext, Throwable cause) {
Options options = OptionsExtension.getOptions(extensionContext);
if (shouldRecordTrace(options)) {
saveTrace(extensionContext);
}
closeBrowserContext();
}
private static void saveTraceWhenOn(ExtensionContext extensionContext) {
Options options = OptionsExtension.getOptions(extensionContext);
if (options.trace.equals(Options.Trace.ON)) {
saveTrace(extensionContext);
}
}
private static void saveTrace(ExtensionContext extensionContext) {
BrowserContext browserContext = threadLocalBrowserContext.get();
if (browserContext == null) {
return;
}
Path outputPath = getOutputPath(extensionContext);
createOutputPath(outputPath);
Tracing.StopOptions stopOptions = new Tracing.StopOptions().setPath(outputPath.resolve("trace.zip"));
browserContext.tracing().stop(stopOptions);
}
private static void createOutputPath(Path outputPath) {
if (!Files.exists(outputPath)) {
try {
Files.createDirectories(outputPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private static Path getOutputPath(ExtensionContext extensionContext) {
BrowserType browserType = BrowserExtension.getBrowser().browserType();
Path defaultOutputPath = getDefaultOutputPath(extensionContext);
String outputDirName = extensionContext.getRequiredTestClass().getName() + "." +
extensionContext.getRequiredTestMethod().getName() + "-" +
browserType.name();
return defaultOutputPath.resolve(outputDirName);
}
private static Path getDefaultOutputPath(ExtensionContext extensionContext) {
Options options = OptionsExtension.getOptions(extensionContext);
Path outputPath = options.outputDir;
if (outputPath == null) {
outputPath = Paths.get(System.getProperty("user.dir")).resolve("test-results");
}
return outputPath;
}
private void closeBrowserContext() {
cleanUpPage();
BrowserContext browserContext = threadLocalBrowserContext.get();
threadLocalBrowserContext.remove();
if (browserContext != null) {
browserContext.close();
}
}
private static boolean shouldRecordTrace(Options options) {
return options.trace.equals(Options.Trace.ON) || options.trace.equals(Options.Trace.RETAIN_ON_FAILURE);
}
private static Browser.NewContextOptions getContextOptions(Playwright playwright, Options options) {
Browser.NewContextOptions contextOptions = Utils.clone(options.contextOptions);
if (contextOptions == null) {
@@ -176,7 +94,7 @@ public class BrowserContextExtension implements ParameterResolver, TestWatcher {
contextOptions.hasTouch = deviceDescriptor.hasTouch;
}
if (options.ignoreHTTPSErrors != null) {
if(options.ignoreHTTPSErrors != null) {
contextOptions.setIgnoreHTTPSErrors(options.ignoreHTTPSErrors);
}
@@ -62,7 +62,7 @@ public class BrowserExtension implements ParameterResolver, AfterAllCallback {
Options options = OptionsExtension.getOptions(extensionContext);
Playwright playwright = PlaywrightExtension.getOrCreatePlaywright(extensionContext);
BrowserType.LaunchOptions launchOptions = getLaunchOptions(options);
BrowserType browserType = playwright.chromium();
if (options.browserName != null) {
@@ -73,31 +73,12 @@ public class BrowserExtension implements ParameterResolver, AfterAllCallback {
browserType = getBrowserTypeForName(playwright, deviceDescriptor.defaultBrowserType);
}
}
if(options.wsEndpoint != null && !options.wsEndpoint.isEmpty()) {
BrowserType.ConnectOptions connectOptions = getConnectOptions(options);
browser = browserType.connect(options.wsEndpoint, connectOptions);
} else {
BrowserType.LaunchOptions launchOptions = getLaunchOptions(options);
browser = browserType.launch(launchOptions);
}
browser = browserType.launch(launchOptions);
threadLocalBrowser.set(browser);
return browser;
}
static Browser getBrowser() {
return threadLocalBrowser.get();
}
private static BrowserType.ConnectOptions getConnectOptions(Options options) {
BrowserType.ConnectOptions connectOptions = options.connectOptions;
if(connectOptions == null) {
connectOptions = new BrowserType.ConnectOptions();
}
return connectOptions;
}
private static BrowserType getBrowserTypeForName(Playwright playwright, String name) {
switch (name) {
case "webkit":
@@ -22,8 +22,17 @@ import org.junit.jupiter.api.extension.*;
import static com.microsoft.playwright.impl.junit.ExtensionUtils.*;
public class PageExtension implements ParameterResolver {
private static final ThreadLocal<Page> threadLocalPage = new ThreadLocal<>();
public class PageExtension implements ParameterResolver, AfterEachCallback {
private static final ThreadLocal<Page> threadLocalPage = new ThreadLocal<>();
@Override
public void afterEach(ExtensionContext extensionContext) {
Page page = threadLocalPage.get();
threadLocalPage.remove();
if (page != null) {
page.close();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
@@ -38,7 +47,6 @@ public class PageExtension implements ParameterResolver {
/**
* Returns the Page that belongs to the current test. Will be created if it doesn't already exist.
* <strong>NOTE:</strong> this method is subject to change.
*
* @param extensionContext the context in which the current test or container is being executed.
* @return The Page that belongs to the current test.
*/
@@ -53,8 +61,4 @@ public class PageExtension implements ParameterResolver {
threadLocalPage.set(page);
return page;
}
static void cleanUpPage() {
threadLocalPage.remove();
}
}
@@ -16,9 +16,10 @@
package com.microsoft.playwright.junit;
import com.microsoft.playwright.*;
import java.nio.file.Path;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Playwright;
/**
* <strong>NOTE:</strong> this API is experimental and is subject to changes.
@@ -42,40 +43,6 @@ public class Options {
public Browser.NewContextOptions contextOptions;
public APIRequest.NewContextOptions apiRequestOptions;
public Playwright.CreateOptions playwrightCreateOptions;
// WebSocket endpoint to be used when connecting to a remote browser.
// If this is set, BrowserType.connect will be used. Otherwise, BrowserType.launch will be used.
public String wsEndpoint;
public BrowserType.ConnectOptions connectOptions;
// The dir where test artifacts will be stored
public Path outputDir;
// When to record traces. Default is OFF.
public Trace trace = Trace.OFF;
public enum Trace {
OFF,
ON,
RETAIN_ON_FAILURE;
}
public Options setTrace(Trace trace) {
this.trace = trace;
return this;
}
public Options setOutputDir(Path outputDir) {
this.outputDir = outputDir;
return this;
}
public Options setWsEndpoint(String wsEndpoint) {
this.wsEndpoint = wsEndpoint;
return this;
}
public Options setConnectOptions(BrowserType.ConnectOptions connectOptions) {
this.connectOptions = connectOptions;
return this;
}
public Options setPlaywrightCreateOptions(Playwright.CreateOptions playwrightCreateOptions) {
this.playwrightCreateOptions = playwrightCreateOptions;
@@ -1,108 +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.options;
import java.nio.file.Path;
public class ClientCertificate {
/**
* Exact origin that the certificate is valid for. Origin includes {@code https} protocol, a hostname and optionally a
* port.
*/
public String origin;
/**
* Path to the file with the certificate in PEM format.
*/
public Path certPath;
/**
* Direct value of the certificate in PEM format.
*/
public byte[] cert;
/**
* Path to the file with the private key in PEM format.
*/
public Path keyPath;
/**
* Direct value of the private key in PEM format.
*/
public byte[] key;
/**
* Path to the PFX or PKCS12 encoded private key and certificate chain.
*/
public Path pfxPath;
/**
* Direct value of the PFX or PKCS12 encoded private key and certificate chain.
*/
public byte[] pfx;
/**
* Passphrase for the private key (PEM or PFX).
*/
public String passphrase;
public ClientCertificate(String origin) {
this.origin = origin;
}
/**
* Path to the file with the certificate in PEM format.
*/
public ClientCertificate setCertPath(Path certPath) {
this.certPath = certPath;
return this;
}
/**
* Direct value of the certificate in PEM format.
*/
public ClientCertificate setCert(byte[] cert) {
this.cert = cert;
return this;
}
/**
* Path to the file with the private key in PEM format.
*/
public ClientCertificate setKeyPath(Path keyPath) {
this.keyPath = keyPath;
return this;
}
/**
* Direct value of the private key in PEM format.
*/
public ClientCertificate setKey(byte[] key) {
this.key = key;
return this;
}
/**
* Path to the PFX or PKCS12 encoded private key and certificate chain.
*/
public ClientCertificate setPfxPath(Path pfxPath) {
this.pfxPath = pfxPath;
return this;
}
/**
* Direct value of the PFX or PKCS12 encoded private key and certificate chain.
*/
public ClientCertificate setPfx(byte[] pfx) {
this.pfx = pfx;
return this;
}
/**
* Passphrase for the private key (PEM or PFX).
*/
public ClientCertificate setPassphrase(String passphrase) {
this.passphrase = passphrase;
return this;
}
}
@@ -1,22 +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.options;
public enum Contrast {
NO_PREFERENCE,
MORE
}
@@ -20,16 +20,15 @@ public class Cookie {
public String name;
public String value;
/**
* Either url or domain / path are required. Optional.
* either url or domain / path are required. Optional.
*/
public String url;
/**
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or
* domain / path are required. Optional.
* either url or domain / path are required Optional.
*/
public String domain;
/**
* Either url or domain / path are required Optional.
* either url or domain / path are required Optional.
*/
public String path;
/**
@@ -54,22 +53,21 @@ public class Cookie {
this.value = value;
}
/**
* Either url or domain / path are required. Optional.
* either url or domain / path are required. Optional.
*/
public Cookie setUrl(String url) {
this.url = url;
return this;
}
/**
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or
* domain / path are required. Optional.
* either url or domain / path are required Optional.
*/
public Cookie setDomain(String domain) {
this.domain = domain;
return this;
}
/**
* Either url or domain / path are required Optional.
* either url or domain / path are required Optional.
*/
public Cookie setPath(String path) {
this.path = path;
@@ -23,7 +23,7 @@ import java.nio.file.Path;
* The {@code FormData} is used create form data that is sent via {@code APIRequestContext}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* .set("firstName", "John")
* .set("lastName", "Doe")
@@ -32,141 +32,6 @@ import java.nio.file.Path;
* }</pre>
*/
public interface FormData {
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist. File
* values can be passed either as {@code Path} or as {@code FilePayload}. Multiple fields with the same name can be added.
*
* <p> The difference between {@link com.microsoft.playwright.FormData#set FormData.set()} and {@link
* com.microsoft.playwright.FormData#append FormData.append()} is that if the specified key already exists, {@link
* com.microsoft.playwright.FormData#set FormData.set()} will overwrite all existing values with the new one, whereas
* {@link com.microsoft.playwright.FormData#append FormData.append()} will append the new value onto the end of the
* existing set of values.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* FormData form = FormData.create()
* // Only name and value are set.
* .append("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .append("attachment", Paths.get("pic.jpg"))
* // Name, value, filename and Content-Type are set.
* .append("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv"))));
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
* @since v1.44
*/
FormData append(String name, String value);
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist. File
* values can be passed either as {@code Path} or as {@code FilePayload}. Multiple fields with the same name can be added.
*
* <p> The difference between {@link com.microsoft.playwright.FormData#set FormData.set()} and {@link
* com.microsoft.playwright.FormData#append FormData.append()} is that if the specified key already exists, {@link
* com.microsoft.playwright.FormData#set FormData.set()} will overwrite all existing values with the new one, whereas
* {@link com.microsoft.playwright.FormData#append FormData.append()} will append the new value onto the end of the
* existing set of values.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* FormData form = FormData.create()
* // Only name and value are set.
* .append("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .append("attachment", Paths.get("pic.jpg"))
* // Name, value, filename and Content-Type are set.
* .append("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv"))));
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
* @since v1.44
*/
FormData append(String name, boolean value);
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist. File
* values can be passed either as {@code Path} or as {@code FilePayload}. Multiple fields with the same name can be added.
*
* <p> The difference between {@link com.microsoft.playwright.FormData#set FormData.set()} and {@link
* com.microsoft.playwright.FormData#append FormData.append()} is that if the specified key already exists, {@link
* com.microsoft.playwright.FormData#set FormData.set()} will overwrite all existing values with the new one, whereas
* {@link com.microsoft.playwright.FormData#append FormData.append()} will append the new value onto the end of the
* existing set of values.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* FormData form = FormData.create()
* // Only name and value are set.
* .append("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .append("attachment", Paths.get("pic.jpg"))
* // Name, value, filename and Content-Type are set.
* .append("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv"))));
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
* @since v1.44
*/
FormData append(String name, int value);
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist. File
* values can be passed either as {@code Path} or as {@code FilePayload}. Multiple fields with the same name can be added.
*
* <p> The difference between {@link com.microsoft.playwright.FormData#set FormData.set()} and {@link
* com.microsoft.playwright.FormData#append FormData.append()} is that if the specified key already exists, {@link
* com.microsoft.playwright.FormData#set FormData.set()} will overwrite all existing values with the new one, whereas
* {@link com.microsoft.playwright.FormData#append FormData.append()} will append the new value onto the end of the
* existing set of values.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* FormData form = FormData.create()
* // Only name and value are set.
* .append("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .append("attachment", Paths.get("pic.jpg"))
* // Name, value, filename and Content-Type are set.
* .append("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv"))));
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
* @since v1.44
*/
FormData append(String name, Path value);
/**
* Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist. File
* values can be passed either as {@code Path} or as {@code FilePayload}. Multiple fields with the same name can be added.
*
* <p> The difference between {@link com.microsoft.playwright.FormData#set FormData.set()} and {@link
* com.microsoft.playwright.FormData#append FormData.append()} is that if the specified key already exists, {@link
* com.microsoft.playwright.FormData#set FormData.set()} will overwrite all existing values with the new one, whereas
* {@link com.microsoft.playwright.FormData#append FormData.append()} will append the new value onto the end of the
* existing set of values.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* FormData form = FormData.create()
* // Only name and value are set.
* .append("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .append("attachment", Paths.get("pic.jpg"))
* // Name, value, filename and Content-Type are set.
* .append("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv"))));
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
* @since v1.44
*/
FormData append(String name, FilePayload value);
/**
* Creates new instance of {@code FormData}.
*
@@ -179,14 +44,14 @@ public interface FormData {
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))))
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
@@ -200,14 +65,14 @@ public interface FormData {
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))))
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
@@ -221,14 +86,14 @@ public interface FormData {
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))))
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
@@ -242,14 +107,14 @@ public interface FormData {
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))))
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
@@ -263,14 +128,14 @@ public interface FormData {
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* // ...
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))))
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
@@ -23,13 +23,6 @@ public class HttpCredentials {
* Restrain sending http credentials on specific origin (scheme://host:port).
*/
public String origin;
/**
* This option only applies to the requests sent from corresponding {@code APIRequestContext} and does not affect requests
* sent from the browser. {@code "always"} - {@code Authorization} header with basic authentication credentials will be
* sent with the each API request. {@code 'unauthorized} - the credentials are only sent when 401 (Unauthorized) response
* with {@code WWW-Authenticate} header is received. Defaults to {@code "unauthorized"}.
*/
public HttpCredentialsSend send;
public HttpCredentials(String username, String password) {
this.username = username;
@@ -42,14 +35,4 @@ public class HttpCredentials {
this.origin = origin;
return this;
}
/**
* This option only applies to the requests sent from corresponding {@code APIRequestContext} and does not affect requests
* sent from the browser. {@code "always"} - {@code Authorization} header with basic authentication credentials will be
* sent with the each API request. {@code 'unauthorized} - the credentials are only sent when 401 (Unauthorized) response
* with {@code WWW-Authenticate} header is received. Defaults to {@code "unauthorized"}.
*/
public HttpCredentials setSend(HttpCredentialsSend send) {
this.send = send;
return this;
}
}
@@ -1,22 +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.options;
public enum HttpCredentialsSend {
UNAUTHORIZED,
ALWAYS
}
@@ -19,7 +19,6 @@ package com.microsoft.playwright.options;
public enum KeyboardModifier {
ALT,
CONTROL,
CONTROLORMETA,
META,
SHIFT
}
@@ -1,35 +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.options;
public class Location {
public String file;
public Integer line;
public Integer column;
public Location(String file) {
this.file = file;
}
public Location setLine(int line) {
this.line = line;
return this;
}
public Location setColumn(int column) {
this.column = column;
return this;
}
}
@@ -133,15 +133,6 @@ public interface RequestOptions {
* @since v1.26
*/
RequestOptions setMaxRedirects(int maxRedirects);
/**
*
*
* @param maxRetries Maximum number of times network errors should be retried. Currently only {@code ECONNRESET} error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to {@code 0} - no
* retries.
* @since v1.46
*/
RequestOptions setMaxRetries(int maxRetries);
/**
* Changes the request method (e.g. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> or <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a>).
@@ -19,15 +19,11 @@ package com.microsoft.playwright;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
class HttpsConfiguratorImpl extends HttpsConfigurator {
@@ -35,7 +31,7 @@ class HttpsConfiguratorImpl extends HttpsConfigurator {
return new HttpsConfiguratorImpl(createSSLContext());
}
HttpsConfiguratorImpl(SSLContext context) {
private HttpsConfiguratorImpl(SSLContext context) {
super(context);
}
@@ -43,8 +39,8 @@ class HttpsConfiguratorImpl extends HttpsConfigurator {
public void configure(HttpsParameters params) {
SSLContext sslContext = getSSLContext();
SSLParameters sslParams = sslContext.getDefaultSSLParameters();
sslParams.setWantClientAuth(true);
params.setWantClientAuth(true);
sslParams.setNeedClientAuth(true);
params.setNeedClientAuth(true);
params.setSSLParameters(sslParams);
}
@@ -20,6 +20,7 @@ import com.sun.net.httpserver.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.file.FileSystems;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -72,8 +73,11 @@ public class Server implements HttpHandler {
} else {
server = HttpServer.create(new InetSocketAddress("localhost", port), 0);
}
server.createContext("/", this);
server.setExecutor(null); // creates a default executor
File cwd = FileSystems.getDefault().getPath(".").toFile();
server.start();
}
@@ -218,7 +222,7 @@ public class Server implements HttpHandler {
}
long contentLength = body.size();
// -1 means no body, 0 means chunked encoding.
exchange.sendResponseHeaders(200, (contentLength == 0 || exchange.getRequestMethod().equals("HEAD")) ? -1 : contentLength);
exchange.sendResponseHeaders(200, contentLength == 0 ? -1 : contentLength);
if (contentLength > 0) {
exchange.getResponseBody().write(body.toByteArray());
}
@@ -1,160 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.sun.net.httpserver.*;
import javax.net.ssl.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.*;
import java.util.*;
public class ServerWithClientCertificate implements HttpHandler {
private final HttpServer server;
final String origin;
final String crossOrigin;
final String url;
KeyStore keyStore;
static ServerWithClientCertificate create(int port) throws IOException {
return new ServerWithClientCertificate(port);
}
private ServerWithClientCertificate(int port) throws IOException {
origin = "https://localhost:" + port;
crossOrigin = "https://127.0.0.1:" + port;;
url = origin + "/index.html";
HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress("localhost", port), 0);
httpsServer.setHttpsConfigurator(new HttpsConfiguratorImpl(loadCertificates()));
server = httpsServer;
server.createContext("/", this);
server.setExecutor(null); // creates a default executor
server.start();
}
public void stop() {
server.stop(0);
}
private SSLContext loadCertificates() {
try {
// Create an SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
// Load the keystore from file
char[] password = "".toCharArray(); // the password you set during the PKCS12 export
keyStore = KeyStore.getInstance("PKCS12");
InputStream fis = HttpsConfiguratorImpl.class.getClassLoader().getResourceAsStream(
"resources/client-certificates/server/server_keystore.p12");
keyStore.load(fis, password);
// Set up the KeyManagerFactory to use the keystore
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, password);
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
List<X509Certificate> certs = new ArrayList<>();
try {
for (String alias : Collections.list(keyStore.aliases())) {
certs.add((X509Certificate) keyStore.getCertificate(alias));
}
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
return certs.toArray(new X509Certificate[0]);
}
public void checkClientTrusted(X509Certificate[] clientCerts, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Initialize the SSL context
sslContext.init(kmf.getKeyManagers(), trustAllCerts, null);
return sslContext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean validateCertChain(Certificate[] clientCerts) {
try {
// Create CertPath from the provided client certificates
CertificateFactory factory = CertificateFactory.getInstance("X.509");
CertPath certPath = factory.generateCertPath(Arrays.asList(clientCerts));
// Extract Trust Anchors from the trust store
Set<TrustAnchor> trustAnchors = new HashSet<>();
for (String alias : Collections.list(keyStore.aliases())) {
X509Certificate trustedCert = (X509Certificate) keyStore.getCertificate(alias);
if (trustedCert != null) {
trustAnchors.add(new TrustAnchor(trustedCert, null));
}
}
// Initialize PKIX parameters
PKIXParameters params = new PKIXParameters(trustAnchors);
params.setRevocationEnabled(false); // Set to true if you want to enable CRL checking
// Validate the certification path
CertPathValidator certPathValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType());
certPathValidator.validate(certPath, params);
return true;
} catch (Exception e) {
return false;
}
}
private static String div(String testId, String message) {
return "<div data-testid='" + testId + "'>" + message + "</div>";
}
@Override
public void handle(HttpExchange exchange) throws IOException {
SSLSession sslSession = ((HttpsExchange) exchange).getSSLSession();
String response = div("servername", sslSession.getPeerHost());
try {
Certificate[] certs = sslSession.getPeerCertificates();
X509Certificate cert = (X509Certificate) certs[0];
exchange.getResponseHeaders().add("Content-Type", "text/html");
if (validateCertChain(certs)) {
exchange.sendResponseHeaders(200, 0);
response += div("message", String.format("Hello %s, your certificate was issued by %s!",
cert.getSubjectX500Principal().getName(), cert.getIssuerX500Principal().getName()));
} else {
response += div("message", String.format("Sorry %s, certificates from %s are not welcome here.",
cert.getSubjectX500Principal().getName(), cert.getIssuerX500Principal().getName()));
exchange.sendResponseHeaders(403, 0);
}
} catch (SSLPeerUnverifiedException e) {
response += div("message", "Sorry, but you need to provide a client certificate to continue.");
exchange.sendResponseHeaders(401, 0);
}
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write(response);
}
}
}
@@ -22,7 +22,6 @@ import com.microsoft.playwright.options.SameSiteAttribute;
import javax.sql.rowset.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.security.Provider;
import java.time.Duration;
import java.time.Instant;
@@ -43,11 +42,9 @@ public class TestBase {
Browser browser;
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
static final boolean isLinux = Utils.getOS() == Utils.OS.LINUX;
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
static final boolean headful;
static final SameSiteAttribute defaultSameSiteCookieValue;
static {
String headfulEnv = System.getenv("HEADFUL");
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
@@ -163,28 +160,14 @@ public class TestBase {
void waitForCondition(BooleanSupplier predicate) {
waitForCondition(predicate, 5_000);
}
void waitForCondition(BooleanSupplier predicate, int timeoutMs) {
page.waitForCondition(predicate, new Page.WaitForConditionOptions().setTimeout(timeoutMs));
}
private static SameSiteAttribute initSameSiteAttribute() {
if (isChromium()) return SameSiteAttribute.LAX;
if (isWebKit() && isLinux) return SameSiteAttribute.LAX;
if (isWebKit() && !isLinux) return SameSiteAttribute.NONE;
if (isWebKit()) return SameSiteAttribute.NONE;
// for firefox version >= 103 'None' is used.
return SameSiteAttribute.NONE;
}
static boolean chromiumVersionLessThan(String a, String b) {
String[] aParts = a.split("\\.");
String[] bParts = b.split("\\.");
for (int i = 0; i < 4; i++) {
int aPart = Integer.parseInt(aParts[i]);
int bPart = Integer.parseInt(bParts[i]);
if (aPart > bPart) return false;
if (aPart < bPart) return true;
}
return false;
}
}

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