1
0
mirror of synced 2026-08-07 16:06:55 +00:00

Compare commits

..

18 Commits

Author SHA1 Message Date
Devin Rousso ce430e375a chore(release): mark 1.62.0 (#1958) 2026-08-03 14:28:27 -06:00
Devin Rousso d76dc9c7c6 chore(driver): cherry-pick 1.62.1 roll (#1961) 2026-08-03 13:43:45 -06:00
Devin Rousso 7de72a6432 chore(driver): roll to 1.62.0 (#1954) 2026-08-03 09:23:04 -07:00
Yury Semikhatsky 24a70eff89 chore: roll to 1.62.0-alpha-2026-07-22 (#1951) 2026-07-22 15:08:48 -07:00
dependabot[bot] 32cf6cd18e chore(deps): bump the actions group with 2 updates (#1945) 2026-07-08 16:59:35 -07:00
Simon Knott e5135db82c chore: Upgrade EsrpRelease task version to 11 (#1943) 2026-06-30 12:04:36 +02:00
Yury Semikhatsky 6234e06280 feat(docker): pre-extract the driver in images to avoid /tmp unpacking (#1938) 2026-06-29 11:31:15 -07:00
Yury Semikhatsky 20e4edd73c chore: roll driver to 1.61.1 (#1941) 2026-06-29 11:06:07 -07:00
Yury Semikhatsky d2d29d446d feat(driver): bundle playwright-core in driver, keep only Node.js in driver-bundle (#1936) 2026-06-19 09:54:36 -07:00
Yury Semikhatsky 43d2601be8 fix(fetch): serialize LocalDate in post data (#1934) 2026-06-18 10:20:37 -07:00
Yury Semikhatsky ace7a1241f fix(driver-bundle): exclude driver binaries from sources JAR (#1933) 2026-06-16 09:06:07 -07:00
Yury Semikhatsky fddd7c3708 feat(docker): add Ubuntu 26.04 (Resolute Raccoon) image (#1932) 2026-06-15 14:50:36 -07:00
Yury Semikhatsky 423cbf4cc7 chore: roll driver to 1.61.0-beta-1781285686000 (#1929) 2026-06-14 10:53:00 -07:00
Yury Semikhatsky e3f2f6fa5b fix(docker): assemble driver on the host for docker builds (#1930) 2026-06-12 14:09:31 -07:00
Yury Semikhatsky 1b7d164873 chore: extract common skill conventions into CLAUDE.md (#1928) 2026-06-12 11:14:19 -07:00
Yury Semikhatsky 7a59a68828 feat: assemble driver from npm instead of CDN (#1927)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 11:09:55 -07:00
dependabot[bot] 07b50c85e3 chore(deps-dev): bump the all group with 2 updates (#1925) 2026-06-12 11:04:05 -07:00
Yury Semikhatsky c949d8398d chore: add playwright-java-release skill (#1924) 2026-05-21 08:38:33 +01:00
96 changed files with 2706 additions and 261 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ extends:
targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
steps:
- checkout: none
- task: EsrpRelease@9
- task: EsrpRelease@11
inputs:
connectedservicename: 'Playwright-ESRP-PME'
usemanagedidentity: true
@@ -0,0 +1,76 @@
---
name: playwright-java-release
description: Prepare a Playwright Java release after the rolling PR has merged — cut the release branch, mark the Maven version, draft the GitHub release, and tick the Java boxes in the internal checklist.
---
Use this skill once the `chore: roll driver to 1.X.0` PR has merged into `main` and the upstream JS `v1.X.0` is published. The rolling work itself is covered by the [[playwright-roll]] skill.
Throughout this doc, replace `X` with the minor version (e.g. `60` for `1.60.0`) and `<user>` with the fork owner (`gh api user --jq .login`).
The full release checklist lives in the private `microsoft/playwright-internal` repo as the `v1.X checklist` issue. Find its number once:
```bash
unset GITHUB_TOKEN
ISSUE=$(gh search issues --repo microsoft/playwright-internal "v1.X checklist" --json number --jq '.[0].number')
```
Tick each Java box incrementally (one PATCH per item) so the issue reflects accurate state if the flow is interrupted:
```bash
gh api repos/microsoft/playwright-internal/issues/$ISSUE --jq '.body' > /tmp/body.md
# edit /tmp/body.md to flip "- [ ]" → "- [x]" on the relevant Java item
gh api repos/microsoft/playwright-internal/issues/$ISSUE -X PATCH --field body=@/tmp/body.md
```
## 1. Cut the release branch
Push `release-1.X` from current `upstream/main` (which now contains the merged roll commit):
```bash
git fetch upstream main
git push upstream upstream/main:refs/heads/release-1.X
```
## 2. Draft the GitHub release
Generate the release notes from the upstream docs:
```bash
cd ~/playwright
node utils/render_release_notes.mjs java 1.X > /tmp/v1.X.0-release-notes.md
```
The renderer leaves JS-isms that need fixing for Java. Apply these substitutions — the list is not exhaustive, eyeball the diff before publishing:
- `toMatchAriaSnapshot()``matchesAriaSnapshot()`
- `toHaveCSS()``hasCSS()` (and other `toHaveX` matchers → `hasX`)
- `browser.on('context')``browser.onContext()`
- `browserContext.on('download' | 'frameattached' | ...)``browserContext.onDownload()` / `onFrameAttached()` / …
Create the draft directly against `release-1.X` — drafting against `main` and retargeting later is fragile because every `gh release edit` rotates the `untagged-<hash>` ID:
```bash
gh release create v1.X.0 --repo microsoft/playwright-java --draft \
--title "v1.X.0" --notes-file /tmp/v1.X.0-release-notes.md --target release-1.X
```
## 3. Bump the Maven version on the release branch
Cut `mark-v-1.X.0` off `upstream/release-1.X`, run `set_maven_version.sh`, and PR back to the release branch:
```bash
git checkout -b mark-v-1.X.0 upstream/release-1.X
./scripts/set_maven_version.sh 1.X.0
git add -u
git commit -m "chore: mark 1.X.0"
git push -u origin mark-v-1.X.0
gh pr create --repo microsoft/playwright-java --head <user>:mark-v-1.X.0 --base release-1.X \
--title "chore: mark 1.X.0" \
--body "Updates Maven version in all modules to \`1.X.0\` for the v1.X release."
```
`set_maven_version.sh` only invokes `mvn versions:set` on `pom.xml`, `tools/*/pom.xml`, and `examples/pom.xml`, but the root invocation cascades through the reactor, so the expected diff is 11 poms: root + `driver/` + `driver-bundle/` + `playwright/` (from the reactor cascade) + 6 under `tools/` + `examples/`, all flipping `1.<prev>.0-SNAPSHOT``1.X.0`. Any other file in the diff is a red flag.
## 4. Publish
The user publishes the draft release manually once the `mark-v-1.X.0` PR is merged. After publishing, CI pushes the artifacts to Maven Central and runs the Docker workflow automatically: https://github.com/microsoft/playwright-java/actions.
-36
View File
@@ -164,39 +164,3 @@ When you've identified a hanging test:
1. Run it in isolation: `mvn -f playwright/pom.xml test -Dtest='TestClass#testMethod'`. If it passes alone, it's a parallel-load flake — note it but move on.
2. If it still hangs in isolation, look for a recent fix in the upstream repo for the *same* test name. Use `git log --oneline tests/library/<spec>.spec.ts` in `~/playwright`. Upstream fixes for client-side hangs are often small and portable (e.g. `about:blank``server.EMPTY_PAGE` from microsoft/playwright#39840 fixed `route-web-socket.spec.ts` arraybuffer hangs — apparently some browser changed the WebSocket origin policy on `about:blank`).
3. When porting an upstream fix, mirror the helper signature change rather than hard-coding workarounds. E.g. if upstream added a `server` parameter to `setupWS`, do the same in Java by injecting `Server server` via the JUnit fixture (`@FixtureTest` already wires up `ServerLifecycle`, so adding `Server server` to the test method signature is enough — no class-level boilerplate). Watch for local-variable shadowing when you add a `Server server` parameter to a method that already has a `WebSocketRoute server` local; rename the local.
## Commit Convention
Semantic commit messages: `label(scope): description`
Labels: `fix`, `feat`, `chore`, `docs`, `test`, `devops`
```bash
git checkout -b fix-39562
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(proxy): handle SOCKS proxy authentication
Fixes: https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
git push origin fix-39562
gh pr create --repo microsoft/playwright-java --head username:fix-39562 \
--title "fix(proxy): handle SOCKS proxy authentication" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
```
Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Branch naming for issue fixes: `fix-<issue-number>`
## Tips & Tricks
- Project checkouts are in the parent directory (`../`).
- use the "gh" cli to interact with GitHub
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Azure login
uses: azure/login@v3
with:
@@ -26,5 +26,5 @@ jobs:
uses: docker/setup-qemu-action@v4
with:
platforms: arm64
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- run: ./utils/docker/publish_docker.sh stable
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
browser: webkit
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up JDK 1.8
uses: actions/setup-java@v5
with:
@@ -73,7 +73,7 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
@@ -108,7 +108,7 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
+2 -2
View File
@@ -13,9 +13,9 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Cache Maven packages
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
strategy:
fail-fast: false
matrix:
flavor: [jammy, noble]
flavor: [jammy, noble, resolute]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
include:
- runs-on: ubuntu-24.04
@@ -34,7 +34,7 @@ jobs:
- runs-on: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build Docker image
run: |
bash utils/docker/build.sh --${{ matrix.arch }} ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Download drivers
run: scripts/download_driver.sh
- name: Regenerate APIs
+48
View File
@@ -0,0 +1,48 @@
# Playwright Java
The Java client is a port of the JavaScript client in `../playwright/packages/playwright-core/src/client/`. When implementing or changing a method, read the corresponding JS file first and mirror its logic.
Project checkouts (including the upstream `playwright` repo) live in the parent directory (`../`). Use the `gh` cli to interact with GitHub.
## Commit Convention
Semantic commit messages: `label(scope): description`
Labels: `fix`, `feat`, `chore`, `docs`, `test`, `devops`
```bash
git checkout -b fix-39562
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(proxy): handle SOCKS proxy authentication
Fixes: https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
# **Never `git push` without an explicit instruction to push.**
git push origin fix-39562
gh pr create --repo microsoft/playwright-java --head <user>:fix-39562 \
--title "fix(proxy): handle SOCKS proxy authentication" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
```
Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Never add test plan to PR description. Keep PR description short — a few bullet points at most.
Branch naming for issue fixes: `fix-<issue-number>`.
**Never amend commits.** Always create a new commit for follow-up changes, even when iterating on an open PR. Amending rewrites history and forces a force-push, losing the incremental review trail. Only amend if the user explicitly says so.
**Never `git push` without an explicit instruction to push.** Applies even when a PR is already open for the branch — additional commits are immediately visible to reviewers. Commit locally, report what was committed, and wait. Only push when the user's message contains "push", "upload", "create PR", "ship it", or equivalent.
## Skills
- **playwright-roll** (`.claude/skills/playwright-roll/SKILL.md`) — roll Playwright Java to a new upstream version: bump the driver, regenerate the API, and port relevant upstream changes.
- **playwright-java-release** (`.claude/skills/playwright-java-release/SKILL.md`) — prepare a release after the rolling PR merges: cut the release branch, mark the Maven version, and draft the GitHub release.
+4 -3
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 and assemble the Playwright driver. The platform-independent `playwright-core` package is assembled once into `driver/src/main/resources/driver/package/`, and the Node.js binary for each platform into `driver-bundle/src/main/resources/driver/<platform>/` (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
```bash
scripts/download_driver.sh
```
Each driver is assembled from the [`playwright-core`](https://www.npmjs.com/package/playwright-core) npm package (version pinned in [scripts/DRIVER_VERSION](scripts/DRIVER_VERSION)) and the matching Node.js binary from https://nodejs.org, the same way the upstream Playwright build does it.
### Building and running the tests with Maven
```bash
@@ -39,10 +41,9 @@ BROWSER=chromium mvn test -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 generated from the upstream Playwright source at the exact commit that produced the driver version in [scripts/DRIVER_VERSION](scripts/DRIVER_VERSION) (resolved via `npm view playwright@<version> gitHead`). `scripts/generate_api.sh` fetches a minimal upstream checkout automatically; set `PW_SRC_DIR` to reuse an existing `microsoft/playwright` checkout instead. To regenerate Java interfaces for the current driver run:
```bash
./scripts/download_driver.sh
./scripts/generate_api.sh
```
+3 -3
View File
@@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->148.0.7778.96<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->150.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->151.0.7922.34<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->153.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
## Documentation
+17 -16
View File
@@ -6,26 +6,27 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
<name>Playwright - Drivers For All Platforms</name>
<name>Playwright - Node.js For All Platforms</name>
<description>
This module includes Playwright driver and related utilities for all supported platforms.
It is intended to be used on the systems where Playwright driver is not preinstalled.
Node.js binaries for the Playwright driver on every supported platform. Can be excluded when
Node.js is preinstalled on the host (see PLAYWRIGHT_NODEJS_PATH).
</description>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- The Node.js binaries for all platforms live in src/main/resources and must not
be packaged into the sources JAR (see issue #1913). -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<excludeResources>true</excludeResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
+17 -2
View File
@@ -6,13 +6,14 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
</parent>
<artifactId>driver</artifactId>
<name>Playwright - Driver</name>
<description>
This module provides API for discovery and launching of Playwright driver.
API for launching the Playwright driver. Bundles the platform-independent playwright-core
package; the Node.js binary comes from the driver-bundle module or a preinstalled Node.js.
</description>
<dependencies>
@@ -21,4 +22,18 @@
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- The playwright-core package lives in src/main/resources and must not be packaged
into the sources JAR (see issue #1913). -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<excludeResources>true</excludeResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -25,12 +25,14 @@ import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestam
/**
* This class provides access to playwright-cli. It can be either preinstalled
* in the host system and its path is passed as a system property or it can be
* loaded from the driver-bundle module if that module is in the classpath.
* in the host system and its path is passed as a system property, or it can be
* loaded from the classpath: the platform-independent driver code ships in the
* driver module and the Node.js binary in the optional driver-bundle module.
*/
public abstract class Driver {
protected final Map<String, String> env = new LinkedHashMap<>(System.getenv());
public static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
public static final String PLAYWRIGHT_DRIVER_DIR = "PLAYWRIGHT_DRIVER_DIR";
private static Driver instance;
@@ -107,9 +109,12 @@ public abstract class Driver {
}
private static Driver newInstance() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
return new PreinstalledDriver(Paths.get(pathFromProperty));
String driverDir = System.getProperty("playwright.cli.dir");
if (driverDir == null) {
driverDir = System.getenv(PLAYWRIGHT_DRIVER_DIR);
}
if (driverDir != null) {
return new PreinstalledDriver(Paths.get(driverDir));
}
String driverImpl =
@@ -30,17 +30,11 @@ public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
private final Path driverTempDir;
private final boolean deleteOnExit;
private Path preinstalledNodePath;
public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
driverTempDir = alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
driverTempDir.toFile().deleteOnExit();
this(createTempDriverDir(), true);
String nodePath = System.getProperty("playwright.nodejs.path");
if (nodePath != null) {
preinstalledNodePath = Paths.get(nodePath);
@@ -51,6 +45,32 @@ public class DriverJar extends Driver {
logMessage("created DriverJar: " + driverTempDir);
}
private DriverJar(Path driverDir, boolean deleteOnExit) {
this.driverTempDir = driverDir;
this.deleteOnExit = deleteOnExit;
if (deleteOnExit) {
driverTempDir.toFile().deleteOnExit();
}
}
private static Path createTempDriverDir() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
return alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
}
// Extracts the driver (playwright-core package and the Node.js binary for the current platform)
// into the given directory, persistently. Point playwright.cli.dir / PLAYWRIGHT_DRIVER_DIR at it
// to run without extracting to a temp directory on every launch. See issue #1268.
public static void installDriverTo(Path driverDir) throws IOException, URISyntaxException {
Files.createDirectories(driverDir);
new DriverJar(driverDir, false).extractDriverToTempDir();
}
@Override
protected void initialize(Boolean installBrowsers) throws Exception {
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
@@ -119,7 +139,21 @@ public class DriverJar extends Driver {
}
void extractDriverToTempDir() throws URISyntaxException, IOException {
URI originalUri = getDriverResourceURI();
extractResourceToDir("driver/package", driverTempDir.resolve("package"));
if (preinstalledNodePath == null) {
String platformResource = "driver/" + platformDir();
if (DriverJar.class.getClassLoader().getResource(platformResource) == null) {
throw new RuntimeException("Failed to find the bundled Node.js for platform '" + platformDir()
+ "'. Add the com.microsoft.playwright:driver-bundle dependency, or set the "
+ PLAYWRIGHT_NODEJS_PATH + " environment variable (or the playwright.nodejs.path system "
+ "property) to point at a preinstalled Node.js.");
}
extractResourceToDir(platformResource, driverTempDir);
}
}
private void extractResourceToDir(String resourcePath, Path destDir) throws URISyntaxException, IOException {
URI originalUri = DriverJar.class.getClassLoader().getResource(resourcePath).toURI();
URI uri = maybeExtractNestedJar(originalUri);
// Create zip filesystem if loading from jar.
@@ -131,14 +165,8 @@ public class DriverJar extends Driver {
// See https://github.com/microsoft/playwright-java/issues/306
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
Files.walk(srcRoot).forEach(fromPath -> {
if (preinstalledNodePath != null) {
String fileName = fromPath.getFileName().toString();
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
return;
}
}
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
Path toPath = driverTempDir.resolve(relative.toString());
Path toPath = destDir.resolve(relative.toString());
try {
if (Files.isDirectory(fromPath)) {
Files.createDirectories(toPath);
@@ -148,7 +176,9 @@ public class DriverJar extends Driver {
toPath.toFile().setExecutable(true, true);
}
}
toPath.toFile().deleteOnExit();
if (deleteOnExit) {
toPath.toFile().deleteOnExit();
}
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
}
@@ -171,7 +201,9 @@ public class DriverJar extends Driver {
Path fromPath = Paths.get(jarUri);
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
Files.copy(fromPath, toPath);
toPath.toFile().deleteOnExit();
if (deleteOnExit) {
toPath.toFile().deleteOnExit();
}
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
+2
View File
@@ -0,0 +1,2 @@
driver/
local-driver/
+2 -2
View File
@@ -6,11 +6,11 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.60.0</playwright.version>
<playwright.version>1.62.0</playwright.version>
</properties>
<dependencies>
<dependency>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -55,6 +55,20 @@ public interface APIResponse {
* @since v1.16
*/
boolean ok();
/**
* Returns SSL and other security information. Resolves to {@code null} for non-HTTPS responses. For redirected requests,
* returns the information for the last request in the redirect chain.
*
* @since v1.61
*/
SecurityDetails securityDetails();
/**
* Returns the IP address and port of the server. Resolves to {@code null} if the server address is not available. For
* redirected requests, returns the information for the last request in the redirect chain.
*
* @since v1.61
*/
ServerAddr serverAddr();
/**
* Contains the status code of the response (e.g., 200 for a success).
*
@@ -73,6 +87,16 @@ public interface APIResponse {
* @since v1.16
*/
String text();
/**
* Returns resource timing information for given response. For redirected requests, returns the information for the last
* request in the redirect chain. When the response is served <a
* href="https://playwright.dev/java/docs/mock#replaying-from-har">from the HAR file</a>, timing information is not
* available and all the values are -1. Find more information at <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">Resource Timing API</a>.
*
* @since v1.62
*/
Timing timing();
/**
* Contains the URL of the response.
*
@@ -449,6 +449,16 @@ public interface BrowserContext extends AutoCloseable {
}
}
class StorageStateOptions {
/**
* Set to {@code true} to include the context's virtual WebAuthn {@link com.microsoft.playwright.BrowserContext#credentials
* BrowserContext.credentials()} (passkeys) in the storage state snapshot. The captured credentials carry their private
* keys, so they can be re-seeded into a later context via the {@code storageState} option or {@link
* com.microsoft.playwright.BrowserContext#setStorageState BrowserContext.setStorageState()}. Note that restoring the
* storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see {@link
* com.microsoft.playwright.Credentials#install Credentials.install()}), and prevent all real authenticators from working
* in this context.
*/
public Boolean credentials;
/**
* 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
@@ -461,6 +471,19 @@ public interface BrowserContext extends AutoCloseable {
*/
public Path path;
/**
* Set to {@code true} to include the context's virtual WebAuthn {@link com.microsoft.playwright.BrowserContext#credentials
* BrowserContext.credentials()} (passkeys) in the storage state snapshot. The captured credentials carry their private
* keys, so they can be re-seeded into a later context via the {@code storageState} option or {@link
* com.microsoft.playwright.BrowserContext#setStorageState BrowserContext.setStorageState()}. Note that restoring the
* storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see {@link
* com.microsoft.playwright.Credentials#install Credentials.install()}), and prevent all real authenticators from working
* in this context.
*/
public StorageStateOptions setCredentials(boolean credentials) {
this.credentials = credentials;
return this;
}
/**
* 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
@@ -563,6 +586,13 @@ public interface BrowserContext extends AutoCloseable {
* @since v1.45
*/
Clock clock();
/**
* Virtual WebAuthn authenticator for this context. Lets tests seed credentials and intercept {@code
* navigator.credentials.create()} / {@code navigator.credentials.get()} ceremonies.
*
* @since v1.61
*/
Credentials credentials();
/**
* Debugger allows to pause and resume the execution.
*
@@ -1462,7 +1492,8 @@ 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, local storage snapshot, IndexedDB snapshot and
* virtual WebAuthn credentials.
*
* @since v1.8
*/
@@ -1470,13 +1501,17 @@ 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, local storage snapshot, IndexedDB snapshot and
* virtual WebAuthn credentials.
*
* @since v1.8
*/
String storageState(StorageStateOptions options);
/**
* Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state.
* Clears the existing cookies, local storage, IndexedDB entries and virtual WebAuthn credentials, and sets the new storage
* state. When the storage state contains credentials, the virtual WebAuthn authenticator is installed (equivalent to
* {@link com.microsoft.playwright.Credentials#install Credentials.install()}), preventing all real authenticators from
* working in this context.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -124,6 +124,10 @@ public interface BrowserType {
}
}
class ConnectOverCDPOptions {
/**
* If specified, browser artifacts (such as traces and downloads) are saved into this directory.
*/
public Path artifactsDir;
/**
* Additional HTTP headers to be sent with connect request. Optional.
*/
@@ -153,6 +157,13 @@ public interface BrowserType {
*/
public Double timeout;
/**
* If specified, browser artifacts (such as traces and downloads) are saved into this directory.
*/
public ConnectOverCDPOptions setArtifactsDir(Path artifactsDir) {
this.artifactsDir = artifactsDir;
return this;
}
/**
* Additional HTTP headers to be sent with connect request. Optional.
*/
@@ -1303,6 +1314,9 @@ public interface BrowserType {
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>NOTE:</strong> Playwright maintains a curated list of arguments for launching the browser. If you launch the browser without Playwright
* and do not pass the exact same arguments, some of Playwright functionality may be broken upon connecting to the browser.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1329,6 +1343,9 @@ public interface BrowserType {
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>NOTE:</strong> Playwright maintains a curated list of arguments for launching the browser. If you launch the browser without Playwright
* and do not pass the exact same arguments, some of Playwright functionality may be broken upon connecting to the browser.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -17,9 +17,12 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.driver.Driver;
import com.microsoft.playwright.impl.driver.jar.DriverJar;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import static java.util.Arrays.asList;
@@ -28,7 +31,13 @@ import static java.util.Arrays.asList;
* Use this class to launch playwright cli.
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException {
// Extract the driver into a fixed directory instead of running the playwright CLI. This is
// handled in Java because it must not require an already-extracted driver. See issue #1268.
if (args.length > 0 && "install-driver".equals(args[0])) {
installDriver(args);
return;
}
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().addAll(asList(args));
@@ -40,4 +49,17 @@ public class CLI {
Process process = pb.start();
System.exit(process.waitFor());
}
private static void installDriver(String[] args) throws IOException, URISyntaxException {
String dir = args.length > 1 ? args[1] : System.getenv(Driver.PLAYWRIGHT_DRIVER_DIR);
if (dir == null) {
System.err.println("Usage: install-driver <dir> (or set the " + Driver.PLAYWRIGHT_DRIVER_DIR
+ " environment variable)");
System.exit(1);
return;
}
Path driverDir = Paths.get(dir);
DriverJar.installDriverTo(driverDir);
System.out.println("Installed Playwright driver into " + driverDir.toAbsolutePath());
}
}
@@ -0,0 +1,243 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@code Credentials} is a virtual WebAuthn authenticator scoped to a {@code BrowserContext}. It lets tests register
* passkeys and answer {@code navigator.credentials.create()} / {@code navigator.credentials.get()} ceremonies in the page,
* without a real authenticator or hardware security key.
*
* <p> There are three common ways to use it:
*
* <p> <strong>Usage: seed a known credential</strong>
* <pre>{@code
* BrowserContext context = browser.newContext();
*
* // A passkey your backend already provisioned for a test user.
* context.credentials().create("example.com", new Credentials.CreateOptions()
* .setId(knownCredentialId) // base64url
* .setUserHandle(knownUserHandle) // base64url
* .setPrivateKey(knownPrivateKey) // base64url PKCS#8 (DER)
* .setPublicKey(knownPublicKey)); // base64url SPKI (DER)
* context.credentials().install();
*
* Page page = context.newPage();
* page.navigate("https://example.com/login");
* // The page's navigator.credentials.get() is answered with the seeded passkey.
* }</pre>
*
* <p> <strong>Usage: capture a credential, then reuse it</strong>
* <pre>{@code
* // setup test: let the app register a passkey, then save it.
* BrowserContext context = browser.newContext();
* context.credentials().install();
*
* Page page = context.newPage();
* page.navigate("https://example.com/register");
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Create a passkey")).click();
*
* // Read back the passkey the page registered — it includes the private key.
* VirtualCredential credential = context.credentials().get(
* new Credentials.GetOptions().setRpId("example.com")).get(0);
* Files.writeString(Paths.get("playwright/.auth/passkey.json"), new Gson().toJson(credential));
* }</pre>
* <pre>{@code
* // later test: seed the captured passkey so the app starts already enrolled.
* VirtualCredential credential = new Gson().fromJson(
* Files.readString(Paths.get("playwright/.auth/passkey.json")), VirtualCredential.class);
* BrowserContext context = browser.newContext();
* context.credentials().create(credential.rpId, new Credentials.CreateOptions()
* .setId(credential.id)
* .setUserHandle(credential.userHandle)
* .setPrivateKey(credential.privateKey)
* .setPublicKey(credential.publicKey));
* context.credentials().install();
*
* Page page = context.newPage();
* page.navigate("https://example.com/login");
* // navigator.credentials.get() resolves the captured passkey — already signed in.
* }</pre>
*
* <p> <strong>Usage: save credentials in the storage state, restore later</strong>
*
* <p> See <a href="https://playwright.dev/java/docs/auth">authentication guide</a> for examples of using saving and resotring
* the storage state.
*
* <p> <strong>Defaults</strong>
*/
public interface Credentials {
class CreateOptions {
/**
* Base64url-encoded credential id. Auto-generated if omitted.
*/
public String id;
/**
* Base64url-encoded PKCS#8 (DER) private key. Auto-generated if omitted.
*/
public String privateKey;
/**
* Base64url-encoded SPKI (DER) public key. Auto-generated if omitted.
*/
public String publicKey;
/**
* Base64url-encoded user handle. Auto-generated if omitted.
*/
public String userHandle;
/**
* Base64url-encoded credential id. Auto-generated if omitted.
*/
public CreateOptions setId(String id) {
this.id = id;
return this;
}
/**
* Base64url-encoded PKCS#8 (DER) private key. Auto-generated if omitted.
*/
public CreateOptions setPrivateKey(String privateKey) {
this.privateKey = privateKey;
return this;
}
/**
* Base64url-encoded SPKI (DER) public key. Auto-generated if omitted.
*/
public CreateOptions setPublicKey(String publicKey) {
this.publicKey = publicKey;
return this;
}
/**
* Base64url-encoded user handle. Auto-generated if omitted.
*/
public CreateOptions setUserHandle(String userHandle) {
this.userHandle = userHandle;
return this;
}
}
class GetOptions {
/**
* Only return the credential with this base64url-encoded id.
*/
public String id;
/**
* Only return credentials for this relying party id.
*/
public String rpId;
/**
* Only return the credential with this base64url-encoded id.
*/
public GetOptions setId(String id) {
this.id = id;
return this;
}
/**
* Only return credentials for this relying party id.
*/
public GetOptions setRpId(String rpId) {
this.rpId = rpId;
return this;
}
}
/**
* Installs the virtual WebAuthn authenticator into the context, overriding {@code navigator.credentials.create()} and
* {@code navigator.credentials.get()} in all current and future pages. Call this before the page first touches {@code
* navigator.credentials}.
*
* <p> Required: until {@link com.microsoft.playwright.Credentials#install Credentials.install()} is called, no interception is
* in place and the page sees the platform's native (or absent) WebAuthn behaviour. Seeding credentials with {@link
* com.microsoft.playwright.Credentials#create Credentials.create()} without installing populates the authenticator, but
* the page will never see those credentials.
*
* @since v1.61
*/
void install();
/**
* Seeds a virtual WebAuthn credential and returns it.
*
* <p> With only {@code rpId}, generates a fresh **ECDSA P-256** keypair, credential id and user handle. The seeded credential
* is discoverable (resident), so the page can resolve it from both username-then-passkey and usernameless passkey flows.
* The returned object carries the private and public keys, so it can be persisted to disk and re-seeded in a later test.
*
* <p> To **import a known credential**, supply all four of {@code id}, {@code userHandle}, {@code privateKey} and {@code
* publicKey} together.
*
* <p> Call {@link com.microsoft.playwright.Credentials#install Credentials.install()} before navigating to a page that uses
* WebAuthn.
*
* @param rpId Relying party id (typically the site's effective domain).
* @since v1.61
*/
default VirtualCredential create(String rpId) {
return create(rpId, null);
}
/**
* Seeds a virtual WebAuthn credential and returns it.
*
* <p> With only {@code rpId}, generates a fresh **ECDSA P-256** keypair, credential id and user handle. The seeded credential
* is discoverable (resident), so the page can resolve it from both username-then-passkey and usernameless passkey flows.
* The returned object carries the private and public keys, so it can be persisted to disk and re-seeded in a later test.
*
* <p> To **import a known credential**, supply all four of {@code id}, {@code userHandle}, {@code privateKey} and {@code
* publicKey} together.
*
* <p> Call {@link com.microsoft.playwright.Credentials#install Credentials.install()} before navigating to a page that uses
* WebAuthn.
*
* @param rpId Relying party id (typically the site's effective domain).
* @since v1.61
*/
VirtualCredential create(String rpId, CreateOptions options);
/**
* Removes a credential from the authenticator by its id. Works for any credential currently held — both those seeded with
* {@link com.microsoft.playwright.Credentials#create Credentials.create()} and those the page registered itself by calling
* {@code navigator.credentials.create()}.
*
* @param id Base64url-encoded credential id.
* @since v1.61
*/
void delete(String id);
/**
* Returns every credential currently held by the authenticator, optionally filtered by {@code rpId} or {@code id}. This
* includes both credentials seeded with {@link com.microsoft.playwright.Credentials#create Credentials.create()} and
* credentials the page registered itself by calling {@code navigator.credentials.create()}.
*
* <p> Each returned credential includes its private and public keys, so a passkey the app just registered can be saved and
* re-seeded into a later test with {@link com.microsoft.playwright.Credentials#create Credentials.create()} — see the
* second example in the class overview.
*
* @since v1.61
*/
default List<VirtualCredential> get() {
return get(null);
}
/**
* Returns every credential currently held by the authenticator, optionally filtered by {@code rpId} or {@code id}. This
* includes both credentials seeded with {@link com.microsoft.playwright.Credentials#create Credentials.create()} and
* credentials the page registered itself by calling {@code navigator.credentials.create()}.
*
* <p> Each returned credential includes its private and public keys, so a passkey the app just registered can be saved and
* re-seeded into a later test with {@link com.microsoft.playwright.Credentials#create Credentials.create()} — see the
* second example in the class overview.
*
* @since v1.61
*/
List<VirtualCredential> get(GetOptions options);
}
@@ -73,6 +73,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -117,6 +124,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public CheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -170,6 +187,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -250,6 +274,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ClickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -308,6 +342,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -381,6 +422,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DblclickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -475,6 +526,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -528,6 +586,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public HoverOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -550,18 +618,12 @@ public interface ElementHandle extends JSHandle {
}
class InputValueOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* @deprecated This option is ignored. The value is returned immediately.
*/
public Double timeout;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* @deprecated This option is ignored. The value is returned immediately.
*/
public InputValueOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -652,7 +714,9 @@ public interface ElementHandle extends JSHandle {
*/
public Path path;
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public Integer quality;
/**
@@ -740,7 +804,9 @@ public interface ElementHandle extends JSHandle {
return this;
}
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public ScreenshotOptions setQuality(int quality) {
this.quality = quality;
@@ -896,6 +962,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -940,6 +1013,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public SetCheckedOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1012,6 +1095,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1065,6 +1155,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public TapOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1142,6 +1242,13 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1186,6 +1293,16 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public UncheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -165,6 +165,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -214,6 +221,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public CheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -275,6 +292,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -355,6 +379,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ClickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -413,6 +447,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -486,6 +527,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DblclickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -558,6 +609,13 @@ public interface Frame {
* @deprecated This option has no effect.
*/
public Boolean noWaitAfter;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -607,6 +665,16 @@ public interface Frame {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DragAndDropOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -1156,6 +1224,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -1215,6 +1290,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public HoverOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -1772,6 +1857,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -1821,6 +1913,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public SetCheckedOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -1963,6 +2065,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2022,6 +2131,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public TapOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2154,6 +2273,13 @@ public interface Frame {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2203,6 +2329,16 @@ public interface Frame {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public UncheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2965,7 +3101,7 @@ public interface Frame {
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link com.microsoft.playwright.Frame#evaluate
* Frame.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = frame.evaluate("document.body");
* ElementHandle bodyHandle = frame.evaluateHandle("document.body");
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -3005,7 +3141,7 @@ public interface Frame {
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link com.microsoft.playwright.Frame#evaluate
* Frame.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = frame.evaluate("document.body");
* ElementHandle bodyHandle = frame.evaluateHandle("document.body");
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -145,6 +145,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -189,6 +196,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public CheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -286,6 +303,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -367,6 +391,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ClickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -426,6 +460,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -500,6 +541,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DblclickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
@@ -560,6 +611,13 @@ public interface Locator {
* @deprecated This option has no effect.
*/
public Boolean noWaitAfter;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -604,6 +662,16 @@ public interface Locator {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DragToOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -1241,6 +1309,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1295,6 +1370,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public HoverOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1710,7 +1795,9 @@ public interface Locator {
*/
public Path path;
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public Integer quality;
/**
@@ -1798,7 +1885,9 @@ public interface Locator {
return this;
}
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public ScreenshotOptions setQuality(int quality) {
this.quality = quality;
@@ -1954,6 +2043,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -1998,6 +2094,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public SetCheckedOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -2070,6 +2176,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -2124,6 +2237,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public TapOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -2222,6 +2345,13 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -2266,6 +2396,16 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public UncheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -2333,6 +2473,26 @@ public interface Locator {
return this;
}
}
class WaitForFunctionOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public WaitForFunctionOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* When the locator points to a list of elements, this returns an array of locators, pointing to their respective elements.
*
@@ -5696,5 +5856,83 @@ public interface Locator {
* @since v1.16
*/
void waitFor(WaitForOptions options);
/**
* Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code
* arg} as a second argument.
*
* <p> This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is
* re-resolved on each retry, so it tolerates the element being re-rendered while waiting.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, this method
* will wait for the promise to resolve before checking its value.
*
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Wait for an attribute to appear:
*
* <p> Passing argument to {@code expression}:
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.62
*/
default void waitForFunction(String expression, Object arg) {
waitForFunction(expression, arg, null);
}
/**
* Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code
* arg} as a second argument.
*
* <p> This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is
* re-resolved on each retry, so it tolerates the element being re-rendered while waiting.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, this method
* will wait for the promise to resolve before checking its value.
*
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Wait for an attribute to appear:
*
* <p> Passing argument to {@code expression}:
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.62
*/
default void waitForFunction(String expression) {
waitForFunction(expression, null);
}
/**
* Returns when {@code expression} returns a truthy value, called with the matching element as a first argument, and {@code
* arg} as a second argument.
*
* <p> This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is
* re-resolved on each retry, so it tolerates the element being re-rendered while waiting.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, this method
* will wait for the promise to resolve before checking its value.
*
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Wait for an attribute to appear:
*
* <p> Passing argument to {@code expression}:
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.62
*/
void waitForFunction(String expression, Object arg, WaitForFunctionOptions options);
}
@@ -435,6 +435,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -484,6 +491,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public CheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -545,6 +562,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -625,6 +649,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ClickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -710,6 +744,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -783,6 +824,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DblclickOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -855,6 +906,13 @@ public interface Page extends AutoCloseable {
* @deprecated This option has no effect.
*/
public Boolean noWaitAfter;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -904,6 +962,16 @@ public interface Page extends AutoCloseable {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public DragAndDropOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -1623,6 +1691,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -1682,6 +1757,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public HoverOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2598,7 +2683,9 @@ public interface Page extends AutoCloseable {
*/
public Path path;
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public Integer quality;
/**
@@ -2707,7 +2794,9 @@ public interface Page extends AutoCloseable {
return this;
}
/**
* The quality of the image, between 0-100. Not applicable to {@code png} images.
* The quality of the image, between 0-100. Not applicable to {@code png} images. For {@code jpeg} the default is {@code
* 80}. For {@code webp}, a quality of {@code 100} (the default) produces a lossless image, while lower values use lossy
* compression.
*/
public ScreenshotOptions setQuality(int quality) {
this.quality = quality;
@@ -2823,6 +2912,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -2872,6 +2968,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public SetCheckedOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -3075,6 +3181,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -3134,6 +3247,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public TapOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -3266,6 +3389,13 @@ public interface Page extends AutoCloseable {
* element.
*/
public Position position;
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public ScrollMode scroll;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -3315,6 +3445,16 @@ public interface Page extends AutoCloseable {
this.position = position;
return this;
}
/**
* Controls whether Playwright scrolls the element into view before performing the action. Defaults to {@code "auto"},
* which scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to {@code
* "none"}, Playwright does not scroll the element and the action fails if the element is not already in the viewport. This
* is useful to assert that an element is reachable by the user without additional scrolling.
*/
public UncheckOptions setScroll(ScrollMode scroll) {
this.scroll = scroll;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -4529,7 +4669,7 @@ public interface Page extends AutoCloseable {
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link com.microsoft.playwright.Page#evaluate
* Page.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = page.evaluate("document.body");
* ElementHandle bodyHandle = page.evaluateHandle("document.body");
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -4571,7 +4711,7 @@ public interface Page extends AutoCloseable {
* <p> {@code ElementHandle} instances can be passed as an argument to the {@link com.microsoft.playwright.Page#evaluate
* Page.evaluate()}:
* <pre>{@code
* ElementHandle bodyHandle = page.evaluate("document.body");
* ElementHandle bodyHandle = page.evaluateHandle("document.body");
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
* bodyHandle.dispose();
* }</pre>
@@ -5464,6 +5604,11 @@ public interface Page extends AutoCloseable {
*
* <p> Navigate to the previous page in history.
*
* <p> <strong>NOTE:</strong> **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache
* across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events.
* Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code
* page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state.
*
* @since v1.8
*/
default Response goBack() {
@@ -5475,6 +5620,11 @@ public interface Page extends AutoCloseable {
*
* <p> Navigate to the previous page in history.
*
* <p> <strong>NOTE:</strong> **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache
* across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events.
* Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code
* page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state.
*
* @since v1.8
*/
Response goBack(GoBackOptions options);
@@ -5484,6 +5634,11 @@ public interface Page extends AutoCloseable {
*
* <p> Navigate to the next page in history.
*
* <p> <strong>NOTE:</strong> **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache
* across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events.
* Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code
* page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state.
*
* @since v1.8
*/
default Response goForward() {
@@ -5495,6 +5650,11 @@ public interface Page extends AutoCloseable {
*
* <p> Navigate to the next page in history.
*
* <p> <strong>NOTE:</strong> **Testing Back/Forward Cache (BFCache) is not supported.** By default, Playwright disables the Back/Forward Cache
* across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events.
* Because BFCache restores unfreeze the DOM without firing these events, using {@code page.goBack()} or {@code
* page.goForward()} to trigger a BFCache restore will result in timeouts and a desynchronized {@code Page} state.
*
* @since v1.8
*/
Response goForward(GoForwardOptions options);
@@ -5808,6 +5968,18 @@ public interface Page extends AutoCloseable {
* @since v1.59
*/
void clearPageErrors();
/**
* Provides access to the page's {@code localStorage} for the current origin. See {@code WebStorage}.
*
* @since v1.61
*/
WebStorage localStorage();
/**
* Provides access to the page's {@code sessionStorage} for the current origin. See {@code WebStorage}.
*
* @since v1.61
*/
WebStorage sessionStorage();
/**
* Returns up to (currently) 200 last console messages from this page. See {@link
* com.microsoft.playwright.Page#onConsoleMessage Page.onConsoleMessage()} for more details.
@@ -7552,8 +7724,8 @@ public interface Page extends AutoCloseable {
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
* TimeoutError}. Passing zero timeout disables this.
*
* <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.
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} will throw if the {@code hasTouch} option of the browser context is
* false.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @since v1.8
@@ -7575,8 +7747,8 @@ public interface Page extends AutoCloseable {
* <p> When all steps combined have not finished during the specified {@code timeout}, this method throws a {@code
* TimeoutError}. Passing zero timeout disables this.
*
* <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.
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} will throw if the {@code hasTouch} option of the browser context is
* false.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @since v1.8
@@ -38,6 +38,13 @@ public interface Screencast {
* The quality of the image, between 0-100.
*/
public Integer quality;
/**
* Specifies the dimensions of screencast frames. The actual frame is scaled to preserve the page's aspect ratio and may be
* smaller than these bounds. If a screencast is already active (e.g. started by tracing or video recording), the existing
* configuration takes precedence and the frame size may exceed these bounds or this option may be ignored. If not
* specified the size will be equal to page viewport scaled down to fit into 800×800.
*/
public Size size;
/**
* Callback that receives JPEG-encoded frame data along with the page viewport size at the time of capture.
@@ -60,6 +67,25 @@ public interface Screencast {
this.quality = quality;
return this;
}
/**
* Specifies the dimensions of screencast frames. The actual frame is scaled to preserve the page's aspect ratio and may be
* smaller than these bounds. If a screencast is already active (e.g. started by tracing or video recording), the existing
* configuration takes precedence and the frame size may exceed these bounds or this option may be ignored. If not
* specified the size will be equal to page viewport scaled down to fit into 800×800.
*/
public StartOptions setSize(int width, int height) {
return setSize(new Size(width, height));
}
/**
* Specifies the dimensions of screencast frames. The actual frame is scaled to preserve the page's aspect ratio and may be
* smaller than these bounds. If a screencast is already active (e.g. started by tracing or video recording), the existing
* configuration takes precedence and the frame size may exceed these bounds or this option may be ignored. If not
* specified the size will be equal to page viewport scaled down to fit into 800×800.
*/
public StartOptions setSize(Size size) {
this.size = size;
return this;
}
}
class ShowOverlayOptions {
/**
@@ -103,6 +129,11 @@ public interface Screencast {
}
}
class ShowActionsOptions {
/**
* Cursor decoration shown for pointer actions. {@code "pointer"} (the default) renders a mouse pointer that animates from
* the previous action point to the next one. {@code "none"} disables the cursor decoration.
*/
public ScreencastCursor cursor;
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
@@ -116,6 +147,14 @@ public interface Screencast {
*/
public AnnotatePosition position;
/**
* Cursor decoration shown for pointer actions. {@code "pointer"} (the default) renders a mouse pointer that animates from
* the previous action point to the next one. {@code "none"} disables the cursor decoration.
*/
public ShowActionsOptions setCursor(ScreencastCursor cursor) {
this.cursor = cursor;
return this;
}
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
@@ -22,6 +22,11 @@ public interface ScreencastFrame {
*/
byte[] data();
/**
* The timestamp of when the frame was presented by the browser, in milliseconds since the Unix epoch.
*/
double timestamp();
/**
* Width of the page viewport at the time the frame was captured.
*/
@@ -201,7 +201,8 @@ public interface Selectors {
* Defines custom attribute name to be used in {@link com.microsoft.playwright.Page#getByTestId Page.getByTestId()}. {@code
* data-testid} is used by default.
*
* @param attributeName Test id attribute name.
* @param attributeName Test id attribute name. To match elements with any of several attributes, pass them as a comma-separated list, e.g.
* {@code "data-pw,data-ti"}.
* @since v1.27
*/
void setTestIdAttribute(String attributeName);
@@ -28,8 +28,8 @@ public interface Touchscreen {
/**
* Dispatches a {@code touchstart} and {@code touchend} event with a single touch at the position ({@code x},{@code y}).
*
* <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.
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Touchscreen#tap Touchscreen.tap()} will throw if the {@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.
@@ -0,0 +1,73 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* WebStorage exposes the page's {@code localStorage} or {@code sessionStorage} for the current origin via an async, <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Storage">browser-consistent</a> API.
*
* <p> Instances are accessed through {@link com.microsoft.playwright.Page#localStorage Page.localStorage()} and {@link
* com.microsoft.playwright.Page#sessionStorage Page.sessionStorage()}.
* <pre>{@code
* page.navigate("https://example.com");
* page.localStorage().setItem("token", "abc");
* String token = page.localStorage().getItem("token");
* List<WebStorageItem> all = page.localStorage().items();
* page.localStorage().removeItem("token");
* page.localStorage().clear();
* }</pre>
*/
public interface WebStorage {
/**
* Returns all items in the storage as name/value pairs.
*
* @since v1.61
*/
List<WebStorageItem> items();
/**
* Returns the value for the given {@code name} if present.
*
* @param name Name of the item to retrieve.
* @since v1.61
*/
String getItem(String name);
/**
* Sets the value for the given {@code name}. Overwrites any existing value for that name.
*
* @param name Name of the item to set.
* @param value New value for the item.
* @since v1.61
*/
void setItem(String name, String value);
/**
* Removes the item with the given {@code name}. No-op if the item is absent.
*
* @param name Name of the item to remove.
* @since v1.61
*/
void removeItem(String name);
/**
* Removes all items from the storage.
*
* @since v1.61
*/
void clear();
}
@@ -22,6 +22,9 @@ import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.SecurityDetails;
import com.microsoft.playwright.options.ServerAddr;
import com.microsoft.playwright.options.Timing;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@@ -85,6 +88,22 @@ class APIResponseImpl implements APIResponse {
return status == 0 || (status >= 200 && status <= 299);
}
@Override
public SecurityDetails securityDetails() {
if (!initializer.has("securityDetails")) {
return null;
}
return gson().fromJson(initializer.get("securityDetails"), SecurityDetails.class);
}
@Override
public ServerAddr serverAddr() {
if (!initializer.has("serverAddr")) {
return null;
}
return gson().fromJson(initializer.get("serverAddr"), ServerAddr.class);
}
@Override
public int status() {
return initializer.get("status").getAsInt();
@@ -100,6 +119,26 @@ class APIResponseImpl implements APIResponse {
return new String(body(), StandardCharsets.UTF_8);
}
@Override
public Timing timing() {
Timing timing;
if (initializer.has("timing")) {
timing = gson().fromJson(initializer.get("timing"), Timing.class);
} else {
timing = new Timing();
timing.startTime = -1;
timing.domainLookupStart = -1;
timing.domainLookupEnd = -1;
timing.connectStart = -1;
timing.secureConnectionStart = -1;
timing.connectEnd = -1;
timing.requestStart = -1;
timing.responseStart = -1;
}
timing.responseEnd = initializer.has("responseEndTiming") ? initializer.get("responseEndTiming").getAsDouble() : -1;
return timing;
}
@Override
public String url() {
return initializer.get("url").getAsString();
@@ -46,6 +46,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final DebuggerImpl debugger;
private final APIRequestContextImpl request;
private final ClockImpl clock;
private final CredentialsImpl credentials;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
@@ -94,6 +95,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this);
credentials = new CredentialsImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
@@ -313,6 +315,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return clock;
}
@Override
public Credentials credentials() {
return credentials;
}
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));
@@ -112,8 +112,8 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override
public Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options) {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
if (!"chromium".equals(name()) && !"webkit".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium and WebKit.");
}
if (options == null) {
options = new ConnectOverCDPOptions();
@@ -110,6 +110,14 @@ class ChannelOwner extends LoggingSupport {
return connection.sendMessageAsync(guid, method, params);
}
// Fire-and-forget: silently drop if the object was collected.
void sendMessageNoReply(String method, JsonObject params) {
if (wasCollected) {
return;
}
connection.sendMessageNoReply(guid, method, params);
}
JsonElement sendMessage(String method) {
return sendMessage(method, new JsonObject(), NO_TIMEOUT);
}
@@ -117,11 +125,12 @@ class ChannelOwner extends LoggingSupport {
JsonElement sendMessage(String method, JsonObject params, Double timeout) {
checkNotCollected();
if (timeout != null) {
params.addProperty("timeout", timeout);
// Timeout is passed in the message metadata, remove potential leftover from serialized options.
params.remove("timeout");
} else if (params.has("timeout")) {
throw new PlaywrightException("Internal error: timeout must be passed explicitly.");
}
return connection.sendMessage(guid, method, params);
return connection.sendMessage(guid, method, params, timeout);
}
private void checkNotCollected() {
@@ -39,6 +39,7 @@ class Message {
JsonObject params;
JsonElement result;
SerializedError error;
JsonObject errorDetails;
JsonArray log;
@Override
@@ -128,17 +129,28 @@ public class Connection {
}
public JsonElement sendMessage(String guid, String method, JsonObject params) {
return root.runUntil(() -> {}, sendMessageAsync(guid, method, params));
return sendMessage(guid, method, params, null);
}
public JsonElement sendMessage(String guid, String method, JsonObject params, Double timeout) {
return root.runUntil(() -> {}, internalSendMessage(guid, method, params, timeout, true, true));
}
public WaitableResult<JsonElement> sendMessageAsync(String guid, String method, JsonObject params) {
return internalSendMessage(guid, method, params, true);
return internalSendMessage(guid, method, params, null, true, true);
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params, boolean sendStack) {
// Fire-and-forget: the server never replies.
public void sendMessageNoReply(String guid, String method, JsonObject params) {
internalSendMessage(guid, method, params, null, false, false);
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params, Double timeout, boolean sendStack, boolean expectsReply) {
int id = ++lastId;
WaitableResult<JsonElement> result = new WaitableResult<>();
callbacks.put(id, result);
if (expectsReply) {
callbacks.put(id, result);
}
JsonObject message = new JsonObject();
message.addProperty("id", id);
message.addProperty("guid", guid);
@@ -146,6 +158,9 @@ public class Connection {
message.add("params", params);
JsonObject metadata = new JsonObject();
metadata.addProperty("wallTime", currentTimeMillis());
if (timeout != null) {
metadata.addProperty("timeout", timeout);
}
JsonArray stack = null;
if (titleReported) {
metadata.addProperty("internal", true);
@@ -175,7 +190,7 @@ public class Connection {
callData.add("stack", stack);
JsonObject stackParams = new JsonObject();
stackParams.add("callData", callData);
internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, false);
internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, null, false, true);
}
return result;
}
@@ -251,16 +266,20 @@ public class Connection {
callback.complete(message.result);
} else {
String callLog = formatCallLog(message.log);
PlaywrightException exception;
if (message.error.error == null) {
callback.completeExceptionally(new PlaywrightException(message.error + callLog));
exception = new PlaywrightException(message.error + callLog);
} else if ("TimeoutError".equals(message.error.error.name)) {
callback.completeExceptionally(new TimeoutError(message.error.error + callLog));
exception = new TimeoutError(message.error.error + callLog);
} else if ("TargetClosedError".equals(message.error.error.name)) {
callback.completeExceptionally(new TargetClosedError(message.error.error + callLog));
exception = new TargetClosedError(message.error.error + callLog);
} else {
callback.completeExceptionally(new DriverException(message.error.error + callLog));
exception = new DriverException(message.error.error + callLog);
}
if (message.errorDetails != null) {
exception = new ServerErrorWithDetails(exception, message.errorDetails, message.log);
}
callback.completeExceptionally(exception);
}
return;
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Credentials;
import com.microsoft.playwright.options.VirtualCredential;
import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static java.util.Arrays.asList;
class CredentialsImpl implements Credentials {
private final BrowserContextImpl context;
CredentialsImpl(BrowserContextImpl context) {
this.context = context;
}
@Override
public void install() {
context.sendMessage("credentialsInstall", new JsonObject(), NO_TIMEOUT);
}
@Override
public VirtualCredential create(String rpId, CreateOptions options) {
JsonObject params = options == null ? new JsonObject() : gson().toJsonTree(options).getAsJsonObject();
params.addProperty("rpId", rpId);
JsonObject json = context.sendMessage("credentialsCreate", params, NO_TIMEOUT).getAsJsonObject();
return gson().fromJson(json.get("credential"), VirtualCredential.class);
}
@Override
public void delete(String id) {
JsonObject params = new JsonObject();
params.addProperty("id", id);
context.sendMessage("credentialsDelete", params, NO_TIMEOUT);
}
@Override
public List<VirtualCredential> get(GetOptions options) {
JsonObject params = options == null ? new JsonObject() : gson().toJsonTree(options).getAsJsonObject();
JsonObject json = context.sendMessage("credentialsGet", params, NO_TIMEOUT).getAsJsonObject();
return asList(gson().fromJson(json.getAsJsonArray("credentials"), VirtualCredential[].class));
}
}
@@ -38,6 +38,7 @@ import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.addFilePathUploadParams;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static com.microsoft.playwright.options.ScreenshotType.WEBP;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
private final FrameImpl frame;
@@ -278,6 +279,8 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
String extension = fileName.substring(extStart).toLowerCase();
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
options.type = JPEG;
} else if (".webp".equals(extension)) {
options.type = WEBP;
}
}
}
@@ -1154,8 +1154,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
FrameExpectResult expect(String expression, FrameExpectOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", expression);
JsonElement json = sendMessage("expect", params, options.timeout);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
FrameExpectResult result = new FrameExpectResult();
try {
sendMessage("expect", params, options.timeout);
result.matches = !options.isNot;
} catch (ServerErrorWithDetails e) {
FrameExpectErrorDetails details = gson().fromJson(e.errorDetails(), FrameExpectErrorDetails.class);
result.matches = options.isNot;
result.received = details.received;
result.errorMessage = details.customErrorMessage == null ? null : "Error: " + details.customErrorMessage;
result.log = e.log();
}
return result;
}
}
@@ -29,6 +29,7 @@ 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.Serialization.serializeArgument;
import static com.microsoft.playwright.impl.Utils.convertType;
class LocatorImpl implements Locator {
@@ -666,6 +667,16 @@ class LocatorImpl implements Locator {
frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true);
}
@Override
public void waitForFunction(String expression, Object arg, WaitForFunctionOptions options) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("strict", true);
params.addProperty("expression", expression);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
frame.sendMessage("waitForFunction", params, frame.timeout(options == null ? null : options.timeout));
}
@Override
public String toString() {
String description = description();
@@ -43,8 +43,14 @@ public class LocatorUtils {
return "internal:describe=" + gson().toJson(description);
}
// Multiple test id attribute names can be joined with a comma. Attribute names cannot contain commas.
private static String encodeTestIdAttributeName(String testIdAttributeName) {
return testIdAttributeName.contains(",") ? gson().toJson(testIdAttributeName) : testIdAttributeName;
}
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
String attributeName = encodeTestIdAttributeName(playwright.selectors.testIdAttributeName);
return "internal:testid=[" + attributeName + "=" + escapeForAttributeSelector(testId, true) + "]";
}
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
@@ -35,6 +35,7 @@ import static com.microsoft.playwright.impl.Serialization.parseError;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static com.microsoft.playwright.options.ScreenshotType.WEBP;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
@@ -47,6 +48,8 @@ public class PageImpl extends ChannelOwner implements Page {
private final MouseImpl mouse;
private final TouchscreenImpl touchscreen;
private final ScreencastImpl screencast;
private final WebStorageImpl localStorage;
private final WebStorageImpl sessionStorage;
final Waitable<?> waitableClosedOrCrashed;
private ViewportSize viewport;
private final Router routes = new Router();
@@ -137,6 +140,8 @@ public class PageImpl extends ChannelOwner implements Page {
mouse = new MouseImpl(this);
touchscreen = new TouchscreenImpl(this);
screencast = new ScreencastImpl(this);
localStorage = new WebStorageImpl(this, "local");
sessionStorage = new WebStorageImpl(this, "session");
frames.add(mainFrame);
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
waitableClosedOrCrashed = createWaitForCloseHelper();
@@ -555,8 +560,13 @@ public class PageImpl extends ChannelOwner implements Page {
try {
if (ownedContext != null) {
ownedContext.close();
} else if (options.runBeforeUnload != null && options.runBeforeUnload) {
sendMessage("runBeforeUnload", new JsonObject(), NO_TIMEOUT);
} else {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject params = new JsonObject();
if (options.reason != null) {
params.addProperty("reason", options.reason);
}
sendMessage("close", params, NO_TIMEOUT);
}
} catch (PlaywrightException exception) {
@@ -1250,6 +1260,8 @@ public class PageImpl extends ChannelOwner implements Page {
String extension = fileName.substring(extStart).toLowerCase();
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
options.type = JPEG;
} else if (".webp".equals(extension)) {
options.type = WEBP;
}
}
}
@@ -1362,6 +1374,16 @@ public class PageImpl extends ChannelOwner implements Page {
return screencast;
}
@Override
public WebStorage localStorage() {
return localStorage;
}
@Override
public WebStorage sessionStorage() {
return sessionStorage;
}
@Override
public void type(String selector, String text, TypeOptions options) {
mainFrame.type(selector, text, convertType(options, Frame.TypeOptions.class));
@@ -122,4 +122,10 @@ class FrameExpectResult {
List<String> log;
}
class FrameExpectErrorDetails {
FrameExpectResult.Received received;
Boolean timedOut;
String customErrorMessage;
}
@@ -20,11 +20,13 @@ import com.microsoft.playwright.ScreencastFrame;
class ScreencastFrameImpl implements ScreencastFrame {
private final byte[] data;
private final double timestamp;
private final int viewportWidth;
private final int viewportHeight;
ScreencastFrameImpl(byte[] data, int viewportWidth, int viewportHeight) {
ScreencastFrameImpl(byte[] data, double timestamp, int viewportWidth, int viewportHeight) {
this.data = data;
this.timestamp = timestamp;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
}
@@ -34,6 +36,11 @@ class ScreencastFrameImpl implements ScreencastFrame {
return data;
}
@Override
public double timestamp() {
return timestamp;
}
@Override
public int viewportWidth() {
return viewportWidth;
@@ -39,14 +39,21 @@ class ScreencastImpl implements Screencast {
}
void handleScreencastFrame(JsonObject params) {
if (onFrame == null) {
return;
try {
if (onFrame != null) {
String dataBase64 = params.get("data").getAsString();
byte[] data = java.util.Base64.getDecoder().decode(dataBase64);
double timestamp = params.get("timestamp").getAsDouble();
int viewportWidth = params.get("viewportWidth").getAsInt();
int viewportHeight = params.get("viewportHeight").getAsInt();
onFrame.accept(new ScreencastFrameImpl(data, timestamp, viewportWidth, viewportHeight));
}
} finally {
// The server sends the next frame only after the previous one is acknowledged.
JsonObject ackParams = new JsonObject();
ackParams.add("frameId", params.get("frameId"));
page.sendMessageAsync("screencastFrameAck", ackParams);
}
String dataBase64 = params.get("data").getAsString();
byte[] data = java.util.Base64.getDecoder().decode(dataBase64);
int viewportWidth = params.get("viewportWidth").getAsInt();
int viewportHeight = params.get("viewportHeight").getAsInt();
onFrame.accept(new ScreencastFrameImpl(data, viewportWidth, viewportHeight));
}
@Override
@@ -58,6 +58,7 @@ class Serialization {
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
.registerTypeAdapter(ServiceWorkerPolicy.class, new ToLowerCaseAndDashSerializer<ServiceWorkerPolicy>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(ScrollMode.class, new ToLowerCaseSerializer<ScrollMode>())
.registerTypeAdapter(ConsoleMessagesFilter.class, new ConsoleMessagesFilterSerializer())
.registerTypeAdapter(AriaSnapshotMode.class, new ToLowerCaseSerializer<AriaSnapshotMode>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
@@ -77,6 +78,7 @@ class Serialization {
static final Gson jsonDataSerializer = new GsonBuilder().disableHtmlEscaping()
.registerTypeAdapter(Date.class, new DateSerializer())
.registerTypeAdapter(LocalDate.class, new LocalDateSerializer())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeSerializer())
.serializeNulls().create();
@@ -571,6 +573,15 @@ class Serialization {
}
}
private static class LocalDateSerializer implements JsonSerializer<LocalDate> {
@Override
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
// LocalDate has no time or zone, so emit the ISO-8601 date (yyyy-MM-dd) as-is to
// avoid shifting the calendar date when converting through a time zone.
return new JsonPrimitive(src.toString());
}
}
private static class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
@Override
public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) {
@@ -0,0 +1,57 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.ArrayList;
import java.util.List;
class ServerErrorWithDetails extends PlaywrightException {
private final JsonObject errorDetails;
private final JsonArray log;
ServerErrorWithDetails(PlaywrightException cause, JsonObject errorDetails, JsonArray log) {
super(cause.getMessage(), cause);
this.errorDetails = errorDetails;
this.log = log;
}
// Rethrown with the calling thread's stack trace, see WaitableResult.get().
ServerErrorWithDetails(ServerErrorWithDetails cause) {
super(cause.getMessage(), cause);
this.errorDetails = cause.errorDetails;
this.log = cause.log;
}
JsonObject errorDetails() {
return errorDetails;
}
List<String> log() {
List<String> result = new ArrayList<>();
if (log != null) {
for (JsonElement e : log) {
result.add(e.getAsString());
}
}
return result;
}
}
@@ -41,7 +41,8 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
{
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
info.addProperty("event", "");
sendWaitInfo(info);
}
JsonObject info = new JsonObject();
info.addProperty("phase", "after");
@@ -51,7 +52,7 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
sendWaitInfo(info);
}
}
@@ -61,14 +62,15 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
JsonObject info = new JsonObject();
info.addProperty("phase", "log");
info.addProperty("message", message);
sendWaitForEventInfo(info);
sendWaitInfo(info);
}
private void sendWaitForEventInfo(JsonObject info) {
info.addProperty("event", "");
private void sendWaitInfo(JsonObject info) {
info.addProperty("waitId", waitId);
JsonObject params = new JsonObject();
params.add("info", info);
channel.sendMessageAsync("waitForEventInfo", params);
try {
channel.sendMessageNoReply("__waitInfo__", info);
} catch (RuntimeException e) {
// Fire-and-forget: never throw to the caller.
}
}
}
@@ -52,6 +52,8 @@ class WaitableResult<T> implements Waitable<T> {
throw new TimeoutError(exception.getMessage(), exception);
} if (exception instanceof TargetClosedError) {
throw new TargetClosedError(exception.getMessage(), exception);
} if (exception instanceof ServerErrorWithDetails) {
throw new ServerErrorWithDetails((ServerErrorWithDetails) exception);
}
throw new PlaywrightException(exception.getMessage(), exception);
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.WebStorage;
import com.microsoft.playwright.options.WebStorageItem;
import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static java.util.Arrays.asList;
class WebStorageImpl implements WebStorage {
private final PageImpl page;
private final String kind;
WebStorageImpl(PageImpl page, String kind) {
this.page = page;
this.kind = kind;
}
private JsonObject createParams() {
JsonObject params = new JsonObject();
params.addProperty("kind", kind);
return params;
}
@Override
public List<WebStorageItem> items() {
JsonObject json = page.sendMessage("webStorageItems", createParams(), NO_TIMEOUT).getAsJsonObject();
return asList(gson().fromJson(json.getAsJsonArray("items"), WebStorageItem[].class));
}
@Override
public String getItem(String name) {
JsonObject params = createParams();
params.addProperty("name", name);
JsonObject json = page.sendMessage("webStorageGetItem", params, NO_TIMEOUT).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
}
@Override
public void setItem(String name, String value) {
JsonObject params = createParams();
params.addProperty("name", name);
params.addProperty("value", value);
page.sendMessage("webStorageSetItem", params, NO_TIMEOUT);
}
@Override
public void removeItem(String name) {
JsonObject params = createParams();
params.addProperty("name", name);
page.sendMessage("webStorageRemoveItem", params, NO_TIMEOUT);
}
@Override
public void clear() {
page.sendMessage("webStorageClear", createParams(), NO_TIMEOUT);
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ScreencastCursor {
NONE,
POINTER
}
@@ -18,5 +18,6 @@ package com.microsoft.playwright.options;
public enum ScreenshotType {
PNG,
JPEG
JPEG,
WEBP
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public enum ScrollMode {
AUTO,
NONE
}
@@ -18,11 +18,11 @@ package com.microsoft.playwright.options;
public class Size {
/**
* Video frame width.
* Max frame width in pixels.
*/
public int width;
/**
* Video frame height.
* Max frame height in pixels.
*/
public int height;
@@ -22,42 +22,42 @@ public class Timing {
*/
public double startTime;
/**
* Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds
* Time immediately before the client starts the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
public double domainLookupStart;
/**
* Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds
* Time immediately after the client ends the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
public double domainLookupEnd;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
* Time immediately before the client starts establishing the connection to the server to retrieve the resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
public double connectStart;
/**
* Time immediately before the browser starts the handshake process to secure the current connection. The value is given in
* Time immediately before the client starts the handshake process to secure the current connection. The value is given in
* milliseconds relative to {@code startTime}, -1 if not available.
*/
public double secureConnectionStart;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
* Time immediately after the client establishes the connection to the server to retrieve the resource. The value is given
* in milliseconds relative to {@code startTime}, -1 if not available.
*/
public double connectEnd;
/**
* Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value
* Time immediately before the client starts requesting the resource from the server, cache, or local resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
public double requestStart;
/**
* Time immediately after the browser receives the first byte of the response from the server, cache, or local resource.
* The value is given in milliseconds relative to {@code startTime}, -1 if not available.
* Time immediately after the client receives the first byte of the response from the server, cache, or local resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
public double responseStart;
/**
* Time immediately after the browser receives the last byte of the resource or immediately before the transport connection
* Time immediately after the client receives the last byte of the resource or immediately before the transport connection
* is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
public double responseEnd;
@@ -0,0 +1,41 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.options;
public class VirtualCredential {
/**
* Base64url-encoded credential id.
*/
public String id;
/**
* Relying party id.
*/
public String rpId;
/**
* Base64url-encoded user handle.
*/
public String userHandle;
/**
* Base64url-encoded PKCS#8 (DER) private key.
*/
public String privateKey;
/**
* Base64url-encoded SPKI (DER) public key.
*/
public String publicKey;
}
@@ -0,0 +1,23 @@
/*
* 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 WebStorageItem {
public String name;
public String value;
}
@@ -131,8 +131,9 @@ public class TestBrowserContextCDPSession extends TestBase {
CDPSession session = page.context().newCDPSession(page);
page.close();
PlaywrightException exception = assertThrows(PlaywrightException.class, session::detach);
assertTrue(exception.getMessage().contains("Target page, context or browser has been closed"), exception.getMessage());
// Like the upstream test, only check that detach fails the error depends on
// whether the session detached before or after the page closed.
assertThrows(PlaywrightException.class, session::detach);
context.close();
}
@@ -28,6 +28,7 @@ import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
@@ -55,6 +56,22 @@ public class TestBrowserContextFetch extends TestBase {
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void getShouldReturnTiming() {
APIResponse response = context.request().get(server.PREFIX + "/simple.json");
assertTrue(response.ok());
Timing timing = response.timing();
assertTrue(timing.startTime > 0, "startTime = " + timing.startTime);
assertTrue(timing.domainLookupEnd >= timing.domainLookupStart);
assertTrue(timing.connectStart >= timing.domainLookupEnd);
assertEquals(-1, timing.secureConnectionStart);
assertTrue(timing.connectEnd >= timing.connectStart);
assertTrue(timing.requestStart >= timing.connectEnd);
assertTrue(timing.responseStart >= timing.requestStart);
assertTrue(timing.responseEnd >= timing.responseStart);
assertTrue(timing.responseEnd < 60_000, "responseEnd = " + timing.responseEnd);
}
@Test
void fetchShouldWork() {
APIResponse response = context.request().fetch(server.PREFIX + "/simple.json");
@@ -533,6 +550,23 @@ public class TestBrowserContextFetch extends TestBase {
assertEquals("{\"date\":\"2024-07-10T18:15:30.000Z\"}", new String(body));
}
public static class LocalDateData {
public String name;
public LocalDate date;
}
@Test
void shouldSupportLocalDateInData() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext();
LocalDateData testData = new LocalDateData();
testData.name = "foo";
testData.date = LocalDate.of(2022, 12, 23);
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
request.post(server.EMPTY_PAGE, RequestOptions.create().setData(testData));
byte[] body = serverRequest.get().postBody;
assertEquals("{\"name\":\"foo\",\"date\":\"2022-12-23\"}", new String(body));
}
@Test
void shouldSupportApplicationXWwwFormUrlencoded() throws ExecutionException, InterruptedException {
Future<Server.Request> req = server.futureRequest("/empty.html");
@@ -741,10 +775,12 @@ public class TestBrowserContextFetch extends TestBase {
});
page.evaluate("() => setTimeout(closeContext, 1000);");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
assertTrue(e.getMessage().contains("Request context disposed") ||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
e = assertThrows(PlaywrightException.class, () -> context.request().post(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
assertTrue(e.getMessage().contains("Request context disposed") ||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
@@ -19,6 +19,7 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.HarMode;
import com.microsoft.playwright.options.HarNotFound;
import com.microsoft.playwright.options.RouteFromHarUpdateContentPolicy;
import com.microsoft.playwright.options.Timing;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.TempDir;
@@ -501,4 +502,36 @@ public class TestBrowserContextHar extends TestBase {
assertNull(page.evaluate("window.result"));
}
}
private void setJsonRoute(String path, String json) {
server.setRoute(path, exchange -> {
exchange.getResponseHeaders().add("Content-Type", "application/json");
byte[] body = json.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
try (OutputStream out = exchange.getResponseBody()) {
out.write(body);
}
});
}
@Test
void shouldNotInterceptAPIRequestContextRequestsByDefault(@TempDir Path tmpDir) {
setJsonRoute("/api/data", "{\"hello\": \"live\"}");
Path harPath = tmpDir.resolve("api.har");
try (BrowserContext context1 = browser.newContext()) {
context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true));
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
page1.request().get(server.PREFIX + "/api/data");
}
// Without the option, the live network is hit.
setJsonRoute("/api/data", "{\"hello\": \"fresh\"}");
try (BrowserContext context2 = browser.newContext()) {
context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK));
Page page2 = context2.newPage();
APIResponse replayed = page2.request().get(server.PREFIX + "/api/data");
assertEquals("{\"hello\": \"fresh\"}", replayed.text());
}
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.VirtualCredential;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextWebAuthn extends TestBase {
private static final String B64URL_TO_BYTES_JS =
" const b64UrlToBytes = s => {\n" +
" let str = s.replace(/-/g, '+').replace(/_/g, '/');\n" +
" while (str.length % 4)\n" +
" str += '=';\n" +
" const bin = atob(str);\n" +
" const u8 = new Uint8Array(bin.length);\n" +
" for (let i = 0; i < bin.length; i++)\n" +
" u8[i] = bin.charCodeAt(i);\n" +
" return u8;\n" +
" };\n";
@Test
void shouldNotInterceptNavigatorCredentialsWithoutInstall() {
// Seed a credential, but do not install the interceptor.
context.credentials().create("localhost");
page.navigate(server.EMPTY_PAGE);
Object intercepted = page.evaluate("() => globalThis.__pwWebAuthnInstalled === true");
assertEquals(false, intercepted);
}
@Test
void shouldSeedKnownCredentialAndAuthenticate() {
// This is the easiest way to create credentials. In practice, this
// probably comes from environment.
VirtualCredential known;
try (BrowserContext source = browser.newContext()) {
known = source.credentials().create("localhost");
}
// A fresh context imports the known credential and signs in with it.
context.credentials().create(known.rpId, new Credentials.CreateOptions()
.setId(known.id)
.setUserHandle(known.userHandle)
.setPrivateKey(known.privateKey)
.setPublicKey(known.publicKey));
context.credentials().install();
page.navigate(server.EMPTY_PAGE);
Map<String, Object> result = (Map<String, Object>) page.evaluate(
"async ({ rpId, credentialId }) => {\n" +
B64URL_TO_BYTES_JS +
" const challenge = crypto.getRandomValues(new Uint8Array(32));\n" +
" const cred = await navigator.credentials.get({\n" +
" publicKey: {\n" +
" challenge,\n" +
" rpId,\n" +
" allowCredentials: [{ type: 'public-key', id: b64UrlToBytes(credentialId) }],\n" +
" userVerification: 'preferred',\n" +
" },\n" +
" });\n" +
" const resp = cred.response;\n" +
" return {\n" +
" id: cred.id,\n" +
" type: cred.type,\n" +
" hasClientData: resp.clientDataJSON.byteLength > 0,\n" +
" hasAuthData: resp.authenticatorData.byteLength > 0,\n" +
" hasSignature: resp.signature.byteLength > 0,\n" +
" authDataFlags: new Uint8Array(resp.authenticatorData)[32],\n" +
" };\n" +
"}", mapOf("rpId", "localhost", "credentialId", known.id));
assertEquals(known.id, result.get("id"));
assertEquals("public-key", result.get("type"));
assertEquals(true, result.get("hasClientData"));
assertEquals(true, result.get("hasAuthData"));
assertEquals(true, result.get("hasSignature"));
// UP (0x01) | UV (0x04) = 0x05
assertEquals(0x05, ((Number) result.get("authDataFlags")).intValue() & 0x05);
// After the credential is deleted, the page can no longer authenticate with it.
context.credentials().delete(known.id);
assertEquals(0, context.credentials().get().size());
Object error = page.evaluate(
"async ({ rpId, credentialId }) => {\n" +
B64URL_TO_BYTES_JS +
" const challenge = crypto.getRandomValues(new Uint8Array(32));\n" +
" try {\n" +
" await navigator.credentials.get({\n" +
" publicKey: {\n" +
" challenge,\n" +
" rpId,\n" +
" allowCredentials: [{ type: 'public-key', id: b64UrlToBytes(credentialId) }],\n" +
" },\n" +
" });\n" +
" return 'no-error';\n" +
" } catch (e) {\n" +
" return e.name;\n" +
" }\n" +
"}", mapOf("rpId", "localhost", "credentialId", known.id));
assertEquals("NotAllowedError", error);
}
@Test
void shouldCapturePageCreatedCredentialAndReuseItInAnotherContext() {
// Setup context: the app registers a passkey via navigator.credentials.create().
String createdId;
VirtualCredential captured;
try (BrowserContext setupContext = browser.newContext()) {
setupContext.credentials().install();
Page setupPage = setupContext.newPage();
setupPage.navigate(server.EMPTY_PAGE);
createdId = (String) setupPage.evaluate(
"async ({ rpId }) => {\n" +
" const challenge = crypto.getRandomValues(new Uint8Array(32));\n" +
" const created = await navigator.credentials.create({\n" +
" publicKey: {\n" +
" challenge,\n" +
" rp: { id: rpId, name: 'Test RP' },\n" +
" user: { id: new Uint8Array([1, 2, 3, 4]), name: 'u', displayName: 'User' },\n" +
" pubKeyCredParams: [{ type: 'public-key', alg: -7 }],\n" +
" authenticatorSelection: { residentKey: 'required', userVerification: 'preferred' },\n" +
" },\n" +
" });\n" +
" return created.id;\n" +
"}", mapOf("rpId", "localhost"));
List<VirtualCredential> credentials = setupContext.credentials().get(
new Credentials.GetOptions().setRpId("localhost"));
assertEquals(1, credentials.size());
captured = credentials.get(0);
assertEquals(createdId, captured.id);
assertTrue(captured.privateKey.matches("^[A-Za-z0-9_-]+$"), captured.privateKey);
assertTrue(captured.publicKey.matches("^[A-Za-z0-9_-]+$"), captured.publicKey);
}
// Reuse the captured passkey in a fresh context and sign in with it.
context.credentials().create(captured.rpId, new Credentials.CreateOptions()
.setId(captured.id)
.setUserHandle(captured.userHandle)
.setPrivateKey(captured.privateKey)
.setPublicKey(captured.publicKey));
context.credentials().install();
page.navigate(server.EMPTY_PAGE);
Object gotId = page.evaluate(
"async ({ rpId }) => {\n" +
" const challenge = crypto.getRandomValues(new Uint8Array(32));\n" +
" // No allowCredentials — relies on the re-seeded credential being discoverable.\n" +
" const cred = await navigator.credentials.get({\n" +
" publicKey: { challenge, rpId, userVerification: 'preferred' },\n" +
" });\n" +
" return cred.id;\n" +
"}", mapOf("rpId", "localhost"));
assertEquals(createdId, gotId);
}
}
@@ -40,10 +40,14 @@ public class TestBrowserTypeBasic extends TestBase {
assertEquals(getBrowserNameFromEnv(), browserType.name());
}
static boolean isChromiumOrWebKit() {
return isChromium() || isWebKit();
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Non-chromium behavior")
@DisabledIf(value="isChromiumOrWebKit", disabledReason="Connecting over CDP is supported in Chromium and WebKit")
void shouldThrowWhenTryingToConnectWithNotChromium() {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> browserType.connectOverCDP("foo"));
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium and WebKit."));
}
}
@@ -259,7 +259,11 @@ public class TestBrowserTypeConnect extends TestBase {
}
assertFalse(browser.isConnected());
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForNavigation(() -> {}));
assertTrue(e.getMessage().contains("Browser closed") || e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
// The surfaced message depends on which call hits the closed connection first.
assertTrue(e.getMessage().contains("Browser closed") ||
e.getMessage().contains("Page closed") ||
e.getMessage().contains("Browser has been closed") ||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
@@ -223,7 +224,9 @@ public class TestClientCertificates extends TestBase {
@Test
@DisabledIf(value="com.microsoft.playwright.TestClientCertificates#isWebKitMacOS", disabledReason="The network connection was lost.")
public void shouldWorkWithBrowserLaunchPersistentContext(@TempDir Path tmpDir) {
// No cleanup: on Windows Chromium may keep chrome_debug.log in the user data dir
// locked briefly after close, failing the deletion.
public void shouldWorkWithBrowserLaunchPersistentContext(@TempDir(cleanup = CleanupMode.NEVER) Path tmpDir) {
BrowserType.LaunchPersistentContextOptions options = new BrowserType.LaunchPersistentContextOptions()
.setIgnoreHTTPSErrors(true) // TODO: remove once we can pass a custom CA.
.setClientCertificates(asList(
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
@@ -46,7 +47,9 @@ public class TestDefaultBrowserContext2 extends TestBase {
private BrowserContext persistentContext;
@TempDir Path tempDir;
// No cleanup: on Windows Chromium may keep chrome_debug.log in the user data dir
// locked briefly after close, failing the deletion.
@TempDir(cleanup = CleanupMode.NEVER) Path tempDir;
@AfterEach
void closePersistentContext() {
@@ -22,6 +22,7 @@ import com.microsoft.playwright.options.HttpCredentials;
import com.microsoft.playwright.options.HttpCredentialsSend;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.RequestOptions;
import com.microsoft.playwright.options.ServerAddr;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -252,6 +253,25 @@ public class TestGlobalFetch extends TestBase {
assertEquals(200, response.status());
}
@Test
void shouldReturnServerAddressFromResponse() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.EMPTY_PAGE);
ServerAddr address = response.serverAddr();
assertNotNull(address);
assertEquals(server.PORT, address.port);
assertTrue(asList("127.0.0.1", "::1").contains(address.ipAddress), address.ipAddress);
request.dispose();
}
@Test
void shouldReturnNullSecurityDetailsForHttpResponse() {
APIRequestContext request = playwright.request().newContext();
APIResponse response = request.get(server.EMPTY_PAGE);
assertNull(response.securityDetails());
request.dispose();
}
@Test
void shouldResolveUrlRelativeToGobalBaseURLOption() {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setBaseURL(server.PREFIX));
@@ -17,12 +17,13 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.KeyboardModifier;
import com.microsoft.playwright.options.ScrollMode;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
@Tag("smoke")
public class TestLocatorClick extends TestBase {
@@ -104,4 +105,24 @@ public class TestLocatorClick extends TestBase {
page.evaluate("result")
);
}
@Test
void shouldNotScrollWhenScrollIsNone() {
page.setContent("<div style='height: 2000px;'>filler</div>\n" +
"<button onclick='window._clicked=true'>click me</button>");
PlaywrightException e = assertThrows(PlaywrightException.class,
() -> page.locator("button").click(new Locator.ClickOptions().setScroll(ScrollMode.NONE).setTimeout(2000)));
assertTrue(e.getMessage().contains("element is outside of the viewport"), e.getMessage());
assertNull(page.evaluate("window._clicked"));
assertEquals(0, page.evaluate("() => window.scrollY"));
}
@Test
void shouldClickInViewportElementWhenScrollIsNone() {
page.setContent("<button onclick='window._clicked=true'>click me</button>\n" +
"<div style='height: 2000px;'></div>");
page.locator("button").click(new Locator.ClickOptions().setScroll(ScrollMode.NONE).setTimeout(2000));
assertEquals(true, page.evaluate("window._clicked"));
assertEquals(0, page.evaluate("() => window.scrollY"));
}
}
@@ -140,4 +140,49 @@ public class TestLocatorMisc extends TestBase{
assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).getByText("data3")).hasText("visible data3");
assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(false)).getByText("data1")).hasText("Hidden data1");
}
@Test
void waitForFunctionShouldWaitForAnAttributeToAppear() {
page.setContent("<button id=toggle>Menu</button>");
page.evaluate("() => setTimeout(() => document.querySelector('#toggle').setAttribute('aria-expanded', 'true'), 500)");
page.locator("#toggle").waitForFunction("element => element.hasAttribute('aria-expanded')");
}
@Test
void waitForFunctionShouldReturnImmediatelyWhenAlreadyTruthy() {
page.setContent("<div id=target>yes</div>");
page.locator("#target").waitForFunction("element => element.textContent === 'yes'");
}
@Test
void waitForFunctionShouldAcceptElementHandleArguments() {
page.setContent("<div id=a></div><div id=b>value</div>");
ElementHandle handle = page.querySelector("#b");
page.locator("#a").waitForFunction("(element, other) => other.textContent === 'value'", handle);
}
@Test
void waitForFunctionShouldThrowWhenPredicateThrows() {
page.setContent("<div id=target>no</div>");
PlaywrightException e = assertThrows(PlaywrightException.class,
() -> page.locator("#target").waitForFunction("() => { throw new Error('oh my'); }"));
assertTrue(e.getMessage().contains("oh my"), e.getMessage());
}
@Test
void waitForFunctionShouldThrowOnStrictModeViolation() {
page.setContent("<div class=x>1</div><div class=x>2</div>");
PlaywrightException e = assertThrows(PlaywrightException.class,
() -> page.locator("div.x").waitForFunction("() => true"));
assertTrue(e.getMessage().contains("strict mode violation"), e.getMessage());
}
@Test
void waitForFunctionShouldRespectTimeout() {
page.setContent("<div id=target>no</div>");
PlaywrightException e = assertThrows(PlaywrightException.class,
() -> page.locator("#target").waitForFunction("element => element.textContent === 'yes'", null,
new Locator.WaitForFunctionOptions().setTimeout(500)));
assertTrue(e.getMessage().contains("Timeout 500ms exceeded"), e.getMessage());
}
}
@@ -82,7 +82,10 @@ public class TestPageAriaSnapshotAI {
"Link with a button <button style=\"cursor: pointer\">Button</button>" +
"</a>");
String snapshot = aiSnapshot(page);
assertTrue(snapshot.contains("link \"Link with a button Button\" [ref=e2] [cursor=pointer]"), snapshot);
// The link's name is redundant - "Link with a button" prints as text and "Button" as the button -
// so it is dropped even though the node is clickable.
assertTrue(snapshot.contains("link [ref=e2] [cursor=pointer]"), snapshot);
assertTrue(snapshot.contains("text: Link with a button"), snapshot);
// The button inside a cursor-pointer link should not get a redundant [cursor=pointer]
assertTrue(snapshot.contains("button \"Button\" [ref=e3]"), snapshot);
assertFalse(snapshot.contains("button \"Button\" [ref=e3] [cursor=pointer]"), snapshot);
@@ -30,7 +30,7 @@ public class TestPageEventPageError extends TestBase {
page.evaluate("async () => {\n" +
" for (let i = 0; i < 301; i++)\n" +
" window.setTimeout(() => { throw new Error('error' + i); }, 0);\n" +
" await new Promise(f => window.setTimeout(f, 100));\n" +
" await new Promise(f => window.setTimeout(f, 2000));\n" +
" }");
List<String> errors = page.pageErrors();
@@ -20,9 +20,11 @@ import com.microsoft.playwright.options.Clip;
import com.microsoft.playwright.options.ScreenshotAnimations;
import com.microsoft.playwright.options.ScreenshotCaret;
import com.microsoft.playwright.options.ScreenshotScale;
import com.microsoft.playwright.options.ScreenshotType;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.io.TempDir;
import org.opentest4j.AssertionFailedError;
import javax.imageio.ImageIO;
@@ -65,6 +67,39 @@ public class TestPageScreenshot extends TestBase {
// expect(screenshot).toMatchSnapshot("screenshot-clip-rect.png");
}
private static void assertWebp(byte[] screenshot) {
// WebP magic: "RIFF" at offset 0, "WEBP" at offset 8.
assertTrue(screenshot.length > 12);
assertEquals("RIFF", new String(screenshot, 0, 4, java.nio.charset.StandardCharsets.US_ASCII));
assertEquals("WEBP", new String(screenshot, 8, 4, java.nio.charset.StandardCharsets.US_ASCII));
}
@Test
void shouldProduceAValidWebpScreenshot() {
page.setViewportSize(300, 300);
page.navigate(server.EMPTY_PAGE);
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP));
assertWebp(screenshot);
}
@Test
void pathOptionShouldDetectWebp(@TempDir Path tmpDir) throws IOException {
page.setViewportSize(300, 300);
page.navigate(server.EMPTY_PAGE);
Path outputPath = tmpDir.resolve("screenshot.webp");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setPath(outputPath));
assertWebp(screenshot);
assertWebp(Files.readAllBytes(outputPath));
}
@Test
void qualityOptionShouldWorkForWebp() {
page.navigate(server.PREFIX + "/grid.html");
byte[] lowQuality = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP).setQuality(0));
byte[] highQuality = page.screenshot(new Page.ScreenshotOptions().setType(ScreenshotType.WEBP).setQuality(100));
assertTrue(lowQuality.length < highQuality.length);
}
static private void rafraf(Page page) {
// Do a double raf since single raf does not
// actually guarantee a new animation frame.
@@ -122,12 +122,12 @@ public class TestScreencast extends TestBase {
}
@Test
void onFrameShouldReceiveViewportSize() {
void onFrameShouldReceiveViewportSizeAndTimestamp() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setViewportSize(1000, 400));
Page page = context.newPage();
try {
List<ScreencastFrame> frames = new ArrayList<>();
page.screencast().start(new Screencast.StartOptions().setOnFrame(frames::add));
page.screencast().start(new Screencast.StartOptions().setOnFrame(frames::add).setSize(500, 400));
page.navigate(server.EMPTY_PAGE);
page.evaluate("() => document.body.style.backgroundColor = 'red'");
page.waitForTimeout(500);
@@ -136,6 +136,7 @@ public class TestScreencast extends TestBase {
for (ScreencastFrame frame : frames) {
assertEquals(1000, frame.viewportWidth());
assertEquals(400, frame.viewportHeight());
assertTrue(frame.timestamp() > 0, "expected a positive timestamp, got " + frame.timestamp());
}
} finally {
context.close();
@@ -51,6 +51,20 @@ public class TestSelectorsGetBy extends TestBase {
assertThat(page.locator("div").getByTestId("Hello")).hasText("Hello world");
}
@Test
void getByTestIdWithCommaSeparatedTestIdAttributesShouldMatchAny() {
page.setContent("<section>\n" +
" <div data-pw='Hello'>first</div>\n" +
" <div data-ti='Hello'>second</div>\n" +
" <div data-testid='Hello'>third</div>\n" +
"</section>");
playwright.selectors().setTestIdAttribute("data-pw,data-ti");
assertThat(page.getByTestId("Hello")).hasCount(2);
assertThat(page.getByTestId("Hello")).hasText(new String[]{"first", "second"});
assertThat(page.mainFrame().getByTestId("Hello")).hasCount(2);
assertThat(page.locator("section").getByTestId("Hello")).hasCount(2);
}
@Test
void shouldUseDataTestidInStrictErrors() {
playwright.selectors().setTestIdAttribute("data-custom-id");
@@ -0,0 +1,132 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.WebStorageItem;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
public class TestWebStorage extends TestBase {
private static Map<String, String> asMap(List<WebStorageItem> items) {
Map<String, String> map = new HashMap<>();
for (WebStorageItem item : items) {
map.put(item.name, item.value);
}
return map;
}
@Test
void localStorageItemsReturnsEmptyListOnFreshOrigin() {
page.navigate(server.EMPTY_PAGE);
assertEquals(0, page.localStorage().items().size());
}
@Test
void localStorageGetItemReturnsNullForMissingKey() {
page.navigate(server.EMPTY_PAGE);
assertNull(page.localStorage().getItem("absent"));
}
@Test
void localStorageSetItemPersistsAndSurfacesInItemsAndGetItem() {
page.navigate(server.EMPTY_PAGE);
page.localStorage().setItem("alpha", "1");
page.localStorage().setItem("beta", "2");
assertEquals(mapOf("alpha", "1", "beta", "2"), asMap(page.localStorage().items()));
assertEquals("1", page.localStorage().getItem("alpha"));
assertEquals("1", page.evaluate("() => localStorage.getItem('alpha')"));
}
@Test
void localStorageSetItemOverwritesExistingValue() {
page.navigate(server.EMPTY_PAGE);
page.localStorage().setItem("k", "first");
page.localStorage().setItem("k", "second");
assertEquals("second", page.localStorage().getItem("k"));
}
@Test
void localStorageRemoveItemRemovesSingleItem() {
page.navigate(server.EMPTY_PAGE);
page.localStorage().setItem("a", "1");
page.localStorage().setItem("b", "2");
page.localStorage().removeItem("a");
assertEquals(mapOf("b", "2"), asMap(page.localStorage().items()));
}
@Test
void localStorageClearEmptiesStorage() {
page.navigate(server.EMPTY_PAGE);
page.localStorage().setItem("a", "1");
page.localStorage().setItem("b", "2");
page.localStorage().clear();
assertEquals(0, page.localStorage().items().size());
}
@Test
void sessionStorageRoundTrip() {
page.navigate(server.EMPTY_PAGE);
assertEquals(0, page.sessionStorage().items().size());
page.sessionStorage().setItem("s1", "v1");
page.sessionStorage().setItem("s2", "v2");
assertEquals(mapOf("s1", "v1", "s2", "v2"), asMap(page.sessionStorage().items()));
assertEquals("v1", page.sessionStorage().getItem("s1"));
page.sessionStorage().removeItem("s1");
assertEquals(mapOf("s2", "v2"), asMap(page.sessionStorage().items()));
page.sessionStorage().clear();
assertEquals(0, page.sessionStorage().items().size());
}
@Test
void localStorageAndSessionStorageAreIndependent() {
page.navigate(server.EMPTY_PAGE);
page.localStorage().setItem("shared", "local");
page.sessionStorage().setItem("shared", "session");
assertEquals("local", page.localStorage().getItem("shared"));
assertEquals("session", page.sessionStorage().getItem("shared"));
page.localStorage().clear();
assertEquals(0, page.localStorage().items().size());
assertEquals("session", page.sessionStorage().getItem("shared"));
}
@Test
void storageMethodsAreScopedToTheCurrentOrigin() {
page.navigate(server.PREFIX + "/empty.html");
page.localStorage().setItem("k", "origin-1");
page.navigate(server.CROSS_PROCESS_PREFIX + "/empty.html");
assertEquals(0, page.localStorage().items().size());
page.localStorage().setItem("k", "origin-2");
page.navigate(server.PREFIX + "/empty.html");
assertEquals("origin-1", page.localStorage().getItem("k"));
}
}
@@ -193,9 +193,7 @@ public class TestWorkers extends TestBase {
page.navigate(server.EMPTY_PAGE);
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
// https://github.com/microsoft/playwright/issues/38919
String expected = isFirefox() ? "10,000.2" : "10\u00A0000,2";
assertEquals(expected, worker.evaluate("() => (10000.20).toLocaleString()"));
assertEquals("10\u00A0000,2", worker.evaluate("() => (10000.20).toLocaleString()"));
context.close();
}
@@ -132,6 +132,30 @@ public class TestInstall {
}
@Test
void canInstallDriverToDirectoryAndReuseIt(@TempDir Path tmpDir) throws Exception {
Path driverDir = tmpDir.resolve("driver");
DriverJar.installDriverTo(driverDir);
// The directory is self-contained: the playwright-core package and the Node.js binary.
assertTrue(Files.exists(driverDir.resolve("package").resolve("cli.js")));
assertTrue(Files.exists(driverDir.resolve(isWindows() ? "node.exe" : "node")));
// Pointing playwright.cli.dir at it must reuse it as-is, without extracting to a temp directory.
System.setProperty("playwright.cli.dir", driverDir.toString());
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
assertEquals(driverDir, driver.driverDir());
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("--version");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Path out = tmpDir.resolve("out.txt");
pb.redirectOutput(out.toFile());
Process p = pb.start();
assertTrue(p.waitFor(1, TimeUnit.MINUTES), "Timed out waiting for version to be printed");
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
assertTrue(stdout.contains("Version "), stdout);
}
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
DriverJar auxDriver = new DriverJar();
auxDriver.extractDriverToTempDir();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 39 KiB

+3 -3
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@@ -48,7 +48,7 @@
<junit.version>5.14.1</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<websocket.version>1.6.0</websocket.version>
<slf4j.version>2.0.17</slf4j.version>
<slf4j.version>2.0.18</slf4j.version>
<opentest4j.version>1.3.0</opentest4j.version>
</properties>
@@ -159,7 +159,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
<version>3.5.6</version>
<configuration>
<properties>
<configurationParameters>
+1 -1
View File
@@ -1 +1 @@
1.60.0
1.62.1
+85 -31
View File
@@ -8,8 +8,12 @@ cd "$(dirname $0)"
if [[ ($1 == '-h') || ($1 == '--help') ]]; then
echo ""
echo "This script for downloading playwright driver for all platforms."
echo "The downloaded files will be put under 'driver-bundle/src/main/resources/driver'."
echo "This script downloads and assembles the Playwright driver for all platforms."
echo "The platform-independent 'playwright-core' npm package is assembled once into the driver"
echo "module ('driver/src/main/resources/driver/package'), and the matching Node.js binary from"
echo "https://nodejs.org for each platform goes into the driver-bundle module"
echo "('driver-bundle/src/main/resources/driver/<platform>'), the same way the upstream"
echo "Playwright build does it."
echo ""
echo "Usage: scripts/download_driver.sh [option]"
echo ""
@@ -19,45 +23,95 @@ if [[ ($1 == '-h') || ($1 == '--help') ]]; then
exit 0
fi
# Ubuntu 24.04-arm64 emulated via qemu has a bug, so we prefer wget over curl.
# See https://github.com/microsoft/playwright-java/issues/1678.
download() {
local url=$1
local out=$2
echo "Downloading $url"
if command -v wget &> /dev/null; then
wget -q -O "$out" "$url"
else
curl --retry 5 --retry-delay 2 -fL -o "$out" "$url"
fi
}
DRIVER_VERSION=$(head -1 ./DRIVER_VERSION)
FILE_PREFIX=playwright-$DRIVER_VERSION
cd ../driver-bundle/src/main/resources
if [[ -d 'driver' ]]; then
echo "Deleting existing drivers from $(pwd)"
rm -rf driver
# Resolve the exact upstream commit that produced this driver version, so that the
# bundled Node.js version matches the driver exactly.
GIT_HEAD=$(npm view playwright@"$DRIVER_VERSION" gitHead)
if [[ -z "$GIT_HEAD" ]]; then
echo "Failed to resolve upstream commit (gitHead) for playwright@$DRIVER_VERSION"
exit 1
fi
mkdir -p driver
cd driver
# The Node.js version used to be pinned in the upstream driver build script. The script was
# removed in microsoft/playwright#41518, so for newer versions we follow the same policy it
# had: the latest Node.js LTS (see upstream utils/build/update-playwright-node.mjs).
NODE_VERSION=$(curl -fsSL "https://raw.githubusercontent.com/microsoft/playwright/$GIT_HEAD/utils/build/build-playwright-driver.sh" 2>/dev/null \
| sed -n 's/^NODE_VERSION="\([^"]*\)".*/\1/p')
if [[ -z "$NODE_VERSION" ]]; then
NODE_VERSION=$(curl -fsSL "https://nodejs.org/dist/index.json" \
| node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.parse(s).find(r=>r.lts).version.slice(1)))")
fi
if [[ -z "$NODE_VERSION" ]]; then
echo "Failed to determine Node.js version for playwright@$DRIVER_VERSION ($GIT_HEAD)"
exit 1
fi
for PLATFORM in mac mac-arm64 linux linux-arm64 win32_x64
echo "Driver version: $DRIVER_VERSION"
echo "Upstream commit: $GIT_HEAD"
echo "Node.js version: $NODE_VERSION"
# The platform-independent driver code (playwright-core) is assembled once into the driver module;
# the Node.js binary for each platform is assembled into the driver-bundle module. See issue #1196.
ROOT="$(cd .. && pwd)"
CORE_DEST="$ROOT/driver/src/main/resources/driver"
NODE_DEST="$ROOT/driver-bundle/src/main/resources/driver"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
# 1. playwright-core package -> driver module (once, shared by every platform).
echo "Assembling playwright-core package to $CORE_DEST/package"
rm -rf "$CORE_DEST/package"
mkdir -p "$CORE_DEST"
CORE_TGZ="$TMP_DIR/playwright-core-$DRIVER_VERSION.tgz"
download "https://registry.npmjs.org/playwright-core/-/playwright-core-$DRIVER_VERSION.tgz" "$CORE_TGZ"
# The npm tarball has a top-level package/ directory, so this creates $CORE_DEST/package.
tar -xzf "$CORE_TGZ" -C "$CORE_DEST"
rm -f "$CORE_TGZ"
# 2. Node.js binary for each platform -> driver-bundle module.
# <java platform dir>:<nodejs platform suffix>:<archive extension>
for ENTRY in \
"mac:darwin-x64:tar.gz" \
"mac-arm64:darwin-arm64:tar.gz" \
"linux:linux-x64:tar.gz" \
"linux-arm64:linux-arm64:tar.gz" \
"win32_x64:win-x64:zip"
do
FILE_NAME=$FILE_PREFIX-$PLATFORM.zip
mkdir $PLATFORM
cd $PLATFORM
echo "Downloading driver for $PLATFORM to $(pwd)"
IFS=':' read -r PLATFORM NODE_SUFFIX ARCHIVE <<< "$ENTRY"
DEST="$NODE_DEST/$PLATFORM"
echo "Assembling Node.js for $PLATFORM to $DEST"
rm -rf "$DEST"
mkdir -p "$DEST"
URL=https://cdn.playwright.dev/builds/driver
if [[ "$DRIVER_VERSION" == *-alpha* || "$DRIVER_VERSION" == *-beta* || "$DRIVER_VERSION" == *-next* ]]; then
URL=$URL/next
fi
URL=$URL/$FILE_NAME
echo "Using url: $URL"
# Ubuntu 24.04-arm64 emulated via qemu has a bug, so we prefer wget over curl.
# See https://github.com/microsoft/playwright-java/issues/1678.
if command -v wget &> /dev/null; then
wget $URL
# Node.js binary and its license from the official Node.js distribution.
NODE_DIR="node-v$NODE_VERSION-$NODE_SUFFIX"
NODE_ARCHIVE="$TMP_DIR/$NODE_DIR.$ARCHIVE"
download "https://nodejs.org/dist/v$NODE_VERSION/$NODE_DIR.$ARCHIVE" "$NODE_ARCHIVE"
if [[ $ARCHIVE == "zip" ]]; then
unzip -joq "$NODE_ARCHIVE" "$NODE_DIR/node.exe" -d "$DEST"
unzip -joq "$NODE_ARCHIVE" "$NODE_DIR/LICENSE" -d "$DEST"
else
curl --retry 5 --retry-delay 2 -fL -O $URL
tar -xzf "$NODE_ARCHIVE" -C "$DEST" --strip-components=2 "$NODE_DIR/bin/node"
tar -xzf "$NODE_ARCHIVE" -C "$DEST" --strip-components=1 "$NODE_DIR/LICENSE"
fi
unzip $FILE_NAME -d .
rm $FILE_NAME
cd -
rm -f "$NODE_ARCHIVE"
done
echo ""
echo "All drivers have been successfully downloaded."
echo "All drivers have been successfully assembled."
echo ""
+31 -18
View File
@@ -6,26 +6,39 @@ set +x
trap 'cd $(pwd -P)' EXIT
cd "$(dirname "$0")/.."
PLAYWRIGHT_CLI="unknown"
case $(uname) in
Darwin)
PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/mac/package/cli.js
;;
Linux)
PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/linux/package/cli.js
;;
MINGW64*)
PLAYWRIGHT_CLI=./driver-bundle/src/main/resources/driver/win32_x64/package/cli.js
;;
*)
echo "Unknown platform '$(uname)'"
exit 1;
;;
esac
DRIVER_VERSION=$(head -1 ./scripts/DRIVER_VERSION)
echo "Updating api.json from $($PLAYWRIGHT_CLI --version)"
# api.json is generated from the upstream Playwright source at the exact commit
# that produced this driver version. Set PW_SRC_DIR to reuse an existing upstream
# checkout, otherwise a minimal one is fetched into a temporary directory.
GIT_HEAD=$(npm view playwright@"$DRIVER_VERSION" gitHead)
if [[ -z "$GIT_HEAD" ]]; then
echo "Failed to resolve upstream commit (gitHead) for playwright@$DRIVER_VERSION"
exit 1
fi
node $PLAYWRIGHT_CLI print-api-json > ./tools/api-generator/src/main/resources/api.json
CLONED_UPSTREAM=""
if [[ -n "$PW_SRC_DIR" ]]; then
UPSTREAM_DIR="$PW_SRC_DIR"
echo "Using upstream Playwright checkout at $UPSTREAM_DIR (PW_SRC_DIR)"
else
UPSTREAM_DIR=$(mktemp -d)
CLONED_UPSTREAM="$UPSTREAM_DIR"
echo "Fetching upstream Playwright source at $GIT_HEAD"
# generateApiJson.js only needs utils/ and docs/, so fetch just those.
git clone --quiet --filter=blob:none --no-checkout https://github.com/microsoft/playwright.git "$UPSTREAM_DIR"
git -C "$UPSTREAM_DIR" sparse-checkout init --cone
git -C "$UPSTREAM_DIR" sparse-checkout set utils docs
git -C "$UPSTREAM_DIR" checkout --quiet "$GIT_HEAD"
fi
echo "Updating api.json from upstream playwright@$DRIVER_VERSION ($GIT_HEAD)"
API_JSON_MODE=1 node "$UPSTREAM_DIR/utils/doclint/generateApiJson.js" \
> ./tools/api-generator/src/main/resources/api.json
if [[ -n "$CLONED_UPSTREAM" ]]; then
rm -rf "$CLONED_UPSTREAM"
fi
mvn compile -f ./tools/api-generator --no-transfer-progress
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -997,10 +997,11 @@ class Interface extends TypeDefinition {
}
void writeTo(List<String> output, String offset) {
if (methods.stream().anyMatch(m -> "create".equals(m.jsonName))) {
// Interfaces with a static factory method, see Method.writeTo.
if (asList("Playwright", "FormData", "RequestOptions").contains(jsonName) && methods.stream().anyMatch(m -> "create".equals(m.jsonName))) {
output.add("import com.microsoft.playwright.impl." + jsonName + "Impl;");
}
if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "FrameLocator", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard", "Tracing", "Video", "Debugger", "Screencast", "WebError").contains(jsonName)) {
if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "FrameLocator", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard", "Tracing", "Video", "Debugger", "Screencast", "WebError", "Credentials", "WebStorage").contains(jsonName)) {
output.add("import com.microsoft.playwright.options.*;");
}
if ("Download".equals(jsonName)) {
@@ -1012,7 +1013,7 @@ class Interface extends TypeDefinition {
if ("Clock".equals(jsonName)) {
output.add("import java.util.Date;");
}
if (asList("Page", "Frame", "ElementHandle", "Locator", "LocatorAssertions", "APIRequest", "Browser", "BrowserContext", "BrowserType", "Route", "Request", "Response", "JSHandle", "ConsoleMessage", "APIResponse", "Playwright", "Debugger", "Screencast", "WebSocketRoute").contains(jsonName)) {
if (asList("Page", "Frame", "ElementHandle", "Locator", "LocatorAssertions", "APIRequest", "Browser", "BrowserContext", "BrowserType", "Route", "Request", "Response", "JSHandle", "ConsoleMessage", "APIResponse", "Playwright", "Debugger", "Screencast", "WebSocketRoute", "Credentials", "WebStorage").contains(jsonName)) {
output.add("import java.util.*;");
}
if (asList("WebSocketRoute").contains(jsonName)) {
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-fatjar</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Test Playwright Command Line FatJar</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
@@ -10,7 +10,6 @@ cd "$(dirname $0)"
PROJECT_DIR=$(mktemp -d)
echo "Creating project in $PROJECT_DIR"
cp -R . $PROJECT_DIR
cp -R ../../driver-bundle/src/test/ $PROJECT_DIR/src/
cp -R ../../playwright/src/test/ $PROJECT_DIR/src/
cd $PROJECT_DIR
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.60.0</version>
<version>1.62.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
+8 -2
View File
@@ -38,14 +38,19 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && \
./scripts/download_driver.sh && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
@@ -62,4 +67,5 @@ RUN cd /tmp/pw-java && \
else \
rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \
fi && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
+8 -2
View File
@@ -38,14 +38,19 @@ ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && \
./scripts/download_driver.sh && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
@@ -53,4 +58,5 @@ RUN cd /tmp/pw-java && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
+62
View File
@@ -0,0 +1,62 @@
FROM ubuntu:resolute
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=America/Los_Angeles
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-resolute"
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
# === INSTALL JDK and Maven ===
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-25-jdk \
# Install utilities required for downloading browsers
wget \
# Install utilities required for downloading driver
unzip \
# For the MSEdge install script
gpg && \
rm -rf /var/lib/apt/lists/* && \
# Create the pwuser
useradd -m -s /bin/bash pwuser
# Ubuntu 22.04 and earlier come with Maven 3.6.3 which fails with
# Java 25, so we install latest Maven from Apache instead.
RUN VERSION=3.9.12 && \
wget -O - https://archive.apache.org/dist/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz | tar zxfv - -C /opt/ && \
ln -s /opt/apache-maven-$VERSION/bin/mvn /usr/local/bin/
ARG PW_TARGET_ARCH
ENV JAVA_HOME=/usr/lib/jvm/java-25-openjdk-${PW_TARGET_ARCH}
# === BAKE BROWSERS INTO IMAGE ===
# Browsers will remain downloaded in `/ms-playwright`.
# Note: make sure to set 777 to the registry so that any user can access
# registry.
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
# Extract the Playwright driver into the image once so the library reuses it instead of unpacking
# it into /tmp on every launch. See https://github.com/microsoft/playwright-java/issues/1268.
ENV PLAYWRIGHT_DRIVER_DIR=/ms-playwright-driver
RUN mkdir /ms-playwright && \
mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH && \
chmod -R 777 $PLAYWRIGHT_DRIVER_DIR
+5 -1
View File
@@ -3,7 +3,7 @@ set -e
set +x
if [[ ($1 == '--help') || ($1 == '-h') || ($1 == '') || ($2 == '') ]]; then
echo "usage: $(basename $0) {--arm64,--amd64} {jammy,noble} playwright:localbuild-noble"
echo "usage: $(basename $0) {--arm64,--amd64} {jammy,noble,resolute} playwright:localbuild-noble"
echo
echo "Build Playwright docker image and tag it as 'playwright:localbuild-noble'."
echo "Once image is built, you can run it with"
@@ -34,4 +34,8 @@ fi
PW_TARGET_ARCH=$(echo $1 | cut -c3-)
# Assemble the driver on the host where npm is available; the Dockerfile picks
# it up via `COPY . /tmp/pw-java`.
../../scripts/download_driver.sh
docker build --platform "${PLATFORM}" --build-arg "PW_TARGET_ARCH=${PW_TARGET_ARCH}" -t "$3" -f "Dockerfile.$2" ../../
+15 -2
View File
@@ -38,6 +38,11 @@ NOBLE_TAGS=(
"v${PW_VERSION}-noble"
)
# Ubuntu 26.04
RESOLUTE_TAGS=(
"v${PW_VERSION}-resolute"
)
tag_and_push() {
local source="$1"
local target="$2"
@@ -74,8 +79,10 @@ publish_docker_images_with_arch_suffix() {
TAGS=("${JAMMY_TAGS[@]}")
elif [[ "$FLAVOR" == "noble" ]]; then
TAGS=("${NOBLE_TAGS[@]}")
elif [[ "$FLAVOR" == "resolute" ]]; then
TAGS=("${RESOLUTE_TAGS[@]}")
else
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'jammy', or 'noble'"
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'jammy', 'noble', or 'resolute'"
exit 1
fi
local ARCH="$2"
@@ -100,8 +107,10 @@ publish_docker_manifest () {
TAGS=("${JAMMY_TAGS[@]}")
elif [[ "$FLAVOR" == "noble" ]]; then
TAGS=("${NOBLE_TAGS[@]}")
elif [[ "$FLAVOR" == "resolute" ]]; then
TAGS=("${RESOLUTE_TAGS[@]}")
else
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'jammy', 'noble'"
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'jammy', 'noble', or 'resolute'"
exit 1
fi
@@ -127,3 +136,7 @@ publish_docker_manifest jammy amd64 arm64
publish_docker_images_with_arch_suffix noble amd64
publish_docker_images_with_arch_suffix noble arm64
publish_docker_manifest noble amd64 arm64
publish_docker_images_with_arch_suffix resolute amd64
publish_docker_images_with_arch_suffix resolute arm64
publish_docker_manifest resolute amd64 arm64