Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c433c1d6e5 |
@@ -1,76 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -7,45 +7,10 @@ Help the user roll to a new version of Playwright.
|
||||
ROLLING.md contains general instructions and scripts.
|
||||
|
||||
Start with running ./scripts/roll_driver.sh to update the version and generate the API to see the state of things.
|
||||
Afterwards, walk through the upstream changes that affect the Java client and port the relevant ones.
|
||||
|
||||
## Determining what to port
|
||||
|
||||
List the upstream commits that touched a client-relevant path since the last release. The paths cover everything that can change the public Java surface or the wire protocol:
|
||||
|
||||
- `docs/src/api/` — the source of truth for `api.json`. Method/option additions, removals, and `langs:` filter changes flow from here.
|
||||
- `packages/playwright-core/src/client/` — the JS client implementation that the Java client mirrors.
|
||||
- `packages/isomorphic/` — selector engines, locator generation/parsing, and aria-snapshot logic shared between client and server. Changes here can affect client-side helpers like `getByRoleSelector`.
|
||||
- `packages/playwright/src/matchers/matchers.ts` — assertion-method definitions. Changes here usually correspond to new options on `LocatorAssertions` / `PageAssertions`.
|
||||
- `packages/protocol/src/protocol.yml` — the wire protocol schema. Method/event additions, parameter renames, and result-shape changes affect what the Java `*Impl` classes need to send/receive.
|
||||
|
||||
```bash
|
||||
cd ~/playwright
|
||||
PREV_TAG=$(git tag | grep -E '^v1\.[0-9]+\.[0-9]+$' | sort -V | tail -1) # e.g. v1.59.1
|
||||
git log "$PREV_TAG"..HEAD --oneline -- \
|
||||
'docs/src/api/' \
|
||||
'packages/playwright-core/src/client/' \
|
||||
'packages/isomorphic/' \
|
||||
'packages/playwright/src/matchers/matchers.ts' \
|
||||
'packages/protocol/src/protocol.yml'
|
||||
```
|
||||
|
||||
Walk that list top-to-bottom (oldest-first is easier — newest is at top, so reverse). For each commit:
|
||||
1. Read the commit (`git show <sha>`) to see what client/protocol/docs changed.
|
||||
2. If it's JS-internal (bundling, dispatcher conventions, electron, mcp, dashboard, trace-viewer, test-runner) — skip.
|
||||
3. If it touches `docs/src/api/` or types, check `langs:` annotations — features marked `langs: js`/`langs: js, python` don't apply to Java.
|
||||
4. If it adds/changes a public API method or option that applies to Java, port it. The api.json regenerated by `roll_driver.sh` already contains the new types/options, so the generated Java interfaces usually pick them up automatically — what's typically missing is the `*Impl` wiring.
|
||||
5. Watch for follow-up reverts — a "feat: X" commit might be undone by a later "Revert X". Check whether the change still exists in HEAD before porting.
|
||||
6. Maintain a running notes file (e.g. `/tmp/roll-notes.md`) listing each upstream PR as ported / skipped / verified-already-supported, with a one-line reason. This file becomes the body of the eventual PR.
|
||||
|
||||
## What to include in the rolling PR
|
||||
|
||||
- Driver version bump
|
||||
- Generated interface diffs from `roll_driver.sh`
|
||||
- `*Impl` wiring for each ported feature
|
||||
- Generator updates (import lists, special-cases) if new types appeared
|
||||
- A small test per new public API surface — listener for new events, basic call for new methods, regression for changed return types
|
||||
- PR description: list each upstream PR ported, each skipped (with reason), and each verified-already-supported
|
||||
Afterwards, work through the list of changes that need to be backported.
|
||||
You can find a list of pull requests that might need to be taking into account in the issue titled "Backport changes".
|
||||
Work through them one-by-one and check off the items that you have handled.
|
||||
Not all of them will be relevant, some might have partially been reverted, etc. - so feel free to check with the upstream release branch.
|
||||
|
||||
Rolling includes:
|
||||
- updating client implementation to match changes in the upstream JS implementation (see ../playwright/packages/playwright-core/src/client)
|
||||
@@ -164,3 +129,40 @@ 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 (`../`).
|
||||
- When updating checkboxes, store the issue content into /tmp and edit it there, then update the issue based on the file
|
||||
- use the "gh" cli to interact with GitHub
|
||||
|
||||
@@ -18,14 +18,6 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
browser: [chromium, firefox, webkit]
|
||||
exclude:
|
||||
# macos-latest is the free M1 runner (3 vCPU / 7 GB); WebKit needs more headroom.
|
||||
# Upstream's webkit matrix runs on macos-15-xlarge for the same reason.
|
||||
- os: macos-latest
|
||||
browser: webkit
|
||||
include:
|
||||
- os: macos-15-xlarge
|
||||
browser: webkit
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -26,51 +26,26 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [jammy, noble, resolute]
|
||||
flavor: [jammy, noble]
|
||||
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
|
||||
include:
|
||||
- runs-on: ubuntu-24.04
|
||||
arch: amd64
|
||||
- runs-on: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
bash utils/docker/build.sh --${{ matrix.arch }} ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
|
||||
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
|
||||
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
|
||||
- name: Start container
|
||||
run: |
|
||||
CONTAINER_ID=$(docker run \
|
||||
--rm \
|
||||
--name playwright-docker-test \
|
||||
--platform linux/${{ matrix.arch }} \
|
||||
--user=pwuser \
|
||||
--workdir /home/pwuser \
|
||||
--shm-size=2g \
|
||||
-e CI \
|
||||
-e PW_MAX_RETRIES \
|
||||
-d -t \
|
||||
playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
|
||||
CONTAINER_ID=$(docker run --rm -e CI -e PW_MAX_RETRIES --ipc=host -v "$(pwd)":/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
|
||||
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Copy repository inside docker container
|
||||
- name: Run test in container
|
||||
run: |
|
||||
docker cp . "$CONTAINER_ID":/home/pwuser/playwright
|
||||
# /root/.m2 was populated as root during image build; move it to
|
||||
# pwuser so the locally-installed SNAPSHOT artifacts resolve.
|
||||
docker exec --user root "$CONTAINER_ID" bash -c '
|
||||
chown -R pwuser /home/pwuser/playwright
|
||||
mv /root/.m2 /home/pwuser/.m2
|
||||
chown -R pwuser /home/pwuser/.m2
|
||||
'
|
||||
|
||||
- name: Run smoke tests in container
|
||||
run: |
|
||||
docker exec "$CONTAINER_ID" /home/pwuser/playwright/tools/test-local-installation/create_project_and_run_tests.sh -Dgroups=smoke
|
||||
docker exec "$CONTAINER_ID" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
|
||||
|
||||
- name: Test ClassLoader
|
||||
run: |
|
||||
docker exec "${CONTAINER_ID}" /home/pwuser/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
|
||||
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
|
||||
|
||||
- name: Stop container
|
||||
run: |
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# 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.
|
||||
+3
-4
@@ -20,14 +20,12 @@ git clone https://github.com/microsoft/playwright-java
|
||||
cd playwright-java
|
||||
```
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
```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
|
||||
@@ -41,9 +39,10 @@ BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
|
||||
|
||||
### Generating API
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```bash
|
||||
./scripts/download_driver.sh
|
||||
./scripts/generate_api.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->149.0.7827.55<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->26.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->151.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
+16
-17
@@ -6,27 +6,26 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.61.0</version>
|
||||
<version>1.59.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
<name>Playwright - Node.js For All Platforms</name>
|
||||
<name>Playwright - Drivers For All Platforms</name>
|
||||
<description>
|
||||
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).
|
||||
This module includes Playwright driver and related utilities for all supported platforms.
|
||||
It is intended to be used on the systems where Playwright driver is not preinstalled.
|
||||
</description>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</project>
|
||||
|
||||
+18
-50
@@ -30,11 +30,17 @@ 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 {
|
||||
this(createTempDriverDir(), true);
|
||||
// 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();
|
||||
String nodePath = System.getProperty("playwright.nodejs.path");
|
||||
if (nodePath != null) {
|
||||
preinstalledNodePath = Paths.get(nodePath);
|
||||
@@ -45,32 +51,6 @@ 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)) {
|
||||
@@ -139,21 +119,7 @@ public class DriverJar extends Driver {
|
||||
}
|
||||
|
||||
void extractDriverToTempDir() throws URISyntaxException, IOException {
|
||||
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 originalUri = getDriverResourceURI();
|
||||
URI uri = maybeExtractNestedJar(originalUri);
|
||||
|
||||
// Create zip filesystem if loading from jar.
|
||||
@@ -165,8 +131,14 @@ 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 = destDir.resolve(relative.toString());
|
||||
Path toPath = driverTempDir.resolve(relative.toString());
|
||||
try {
|
||||
if (Files.isDirectory(fromPath)) {
|
||||
Files.createDirectories(toPath);
|
||||
@@ -176,9 +148,7 @@ public class DriverJar extends Driver {
|
||||
toPath.toFile().setExecutable(true, true);
|
||||
}
|
||||
}
|
||||
if (deleteOnExit) {
|
||||
toPath.toFile().deleteOnExit();
|
||||
}
|
||||
toPath.toFile().deleteOnExit();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
|
||||
}
|
||||
@@ -201,9 +171,7 @@ public class DriverJar extends Driver {
|
||||
Path fromPath = Paths.get(jarUri);
|
||||
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
|
||||
Files.copy(fromPath, toPath);
|
||||
if (deleteOnExit) {
|
||||
toPath.toFile().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);
|
||||
-24
@@ -132,30 +132,6 @@ 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();
|
||||
+2
-17
@@ -6,14 +6,13 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.61.0</version>
|
||||
<version>1.59.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
<name>Playwright - Driver</name>
|
||||
<description>
|
||||
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.
|
||||
This module provides API for discovery and launching of Playwright driver.
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
@@ -22,18 +21,4 @@
|
||||
<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,14 +25,12 @@ 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 classpath: the platform-independent driver code ships in the
|
||||
* driver module and the Node.js binary in the optional driver-bundle module.
|
||||
* in the host system and its path is passed as a system property or it can be
|
||||
* loaded from the driver-bundle module if that module is in the classpath.
|
||||
*/
|
||||
public abstract class Driver {
|
||||
protected final Map<String, String> env = new LinkedHashMap<>(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;
|
||||
|
||||
@@ -109,12 +107,9 @@ public abstract class Driver {
|
||||
}
|
||||
|
||||
private static Driver newInstance() throws Exception {
|
||||
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 pathFromProperty = System.getProperty("playwright.cli.dir");
|
||||
if (pathFromProperty != null) {
|
||||
return new PreinstalledDriver(Paths.get(pathFromProperty));
|
||||
}
|
||||
|
||||
String driverImpl =
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
driver/
|
||||
local-driver/
|
||||
+2
-2
@@ -6,11 +6,11 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.61.0</version>
|
||||
<version>1.59.0</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<playwright.version>1.61.0</playwright.version>
|
||||
<playwright.version>1.59.0</playwright.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.61.0</version>
|
||||
<version>1.59.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
|
||||
@@ -23,23 +23,24 @@ import java.nio.file.Path;
|
||||
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
|
||||
* environment or the service to your e2e test.
|
||||
*
|
||||
* <p> Each Playwright browser context has an associated {@code APIRequestContext}, accessible via {@link
|
||||
* com.microsoft.playwright.BrowserContext#request BrowserContext.request()} or {@link
|
||||
* com.microsoft.playwright.Page#request Page.request()} (these return the
|
||||
*
|
||||
* <p> **same instance** — {@code page.request} is a shortcut for {@code page.context().request}). You can also create a
|
||||
* standalone, isolated instance with {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}.
|
||||
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage
|
||||
* with the browser context and can be accessed via {@link com.microsoft.playwright.BrowserContext#request
|
||||
* BrowserContext.request()} or {@link com.microsoft.playwright.Page#request Page.request()}. It is also possible to create
|
||||
* a new APIRequestContext instance manually by calling {@link com.microsoft.playwright.APIRequest#newContext
|
||||
* APIRequest.newContext()}.
|
||||
*
|
||||
* <p> <strong>Cookie management</strong>
|
||||
*
|
||||
* <p> The {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request
|
||||
* BrowserContext.request()} and
|
||||
* <p> {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request BrowserContext.request()}
|
||||
* and {@link com.microsoft.playwright.Page#request Page.request()} shares cookie storage with the corresponding {@code
|
||||
* BrowserContext}. Each API request will have {@code Cookie} header populated with the values from the browser context. If
|
||||
* the API response contains {@code Set-Cookie} header it will automatically update {@code BrowserContext} cookies and
|
||||
* requests made from the page will pick them up. This means that if you log in using this API, your e2e test will be
|
||||
* logged in and vice versa.
|
||||
*
|
||||
* <p> {@link com.microsoft.playwright.Page#request Page.request()} uses the same cookie jar as its {@code BrowserContext}:
|
||||
*
|
||||
* <p> If you want API requests that do **not** share cookies with the browser, create an isolated context via {@link
|
||||
* com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have
|
||||
* its own isolated cookie storage.
|
||||
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
|
||||
* calling {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext}
|
||||
* object will have its own isolated cookie storage.
|
||||
*/
|
||||
public interface APIRequestContext {
|
||||
class DisposeOptions {
|
||||
@@ -483,11 +484,5 @@ public interface APIRequestContext {
|
||||
* @since v1.16
|
||||
*/
|
||||
String storageState(StorageStateOptions options);
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
Tracing tracing();
|
||||
}
|
||||
|
||||
|
||||
@@ -55,20 +55,6 @@ 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).
|
||||
*
|
||||
|
||||
@@ -43,15 +43,6 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public interface Browser extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Emitted when a new browser context is created.
|
||||
*/
|
||||
void onContext(Consumer<BrowserContext> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onContext onContext(handler)}.
|
||||
*/
|
||||
void offContext(Consumer<BrowserContext> handler);
|
||||
|
||||
/**
|
||||
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following:
|
||||
* <ul>
|
||||
|
||||
@@ -114,48 +114,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
void offDialog(Consumer<Dialog> handler);
|
||||
|
||||
/**
|
||||
* Emitted when attachment download started in any page belonging to this context. User can access basic file operations on
|
||||
* downloaded content via the passed {@code Download} instance. See also {@link com.microsoft.playwright.Page#onDownload
|
||||
* Page.onDownload()} to receive events about a specific page.
|
||||
*/
|
||||
void onDownload(Consumer<Download> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onDownload onDownload(handler)}.
|
||||
*/
|
||||
void offDownload(Consumer<Download> handler);
|
||||
|
||||
/**
|
||||
* Emitted when a frame is attached in any page belonging to this context. See also {@link
|
||||
* com.microsoft.playwright.Page#onFrameAttached Page.onFrameAttached()} to receive events about a specific page.
|
||||
*/
|
||||
void onFrameAttached(Consumer<Frame> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onFrameAttached onFrameAttached(handler)}.
|
||||
*/
|
||||
void offFrameAttached(Consumer<Frame> handler);
|
||||
|
||||
/**
|
||||
* Emitted when a frame is detached in any page belonging to this context. See also {@link
|
||||
* com.microsoft.playwright.Page#onFrameDetached Page.onFrameDetached()} to receive events about a specific page.
|
||||
*/
|
||||
void onFrameDetached(Consumer<Frame> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onFrameDetached onFrameDetached(handler)}.
|
||||
*/
|
||||
void offFrameDetached(Consumer<Frame> handler);
|
||||
|
||||
/**
|
||||
* Emitted when a frame is navigated to a new url in any page belonging to this context. See also {@link
|
||||
* com.microsoft.playwright.Page#onFrameNavigated Page.onFrameNavigated()} to receive events about navigations in a
|
||||
* specific page.
|
||||
*/
|
||||
void onFrameNavigated(Consumer<Frame> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onFrameNavigated onFrameNavigated(handler)}.
|
||||
*/
|
||||
void offFrameNavigated(Consumer<Frame> handler);
|
||||
|
||||
/**
|
||||
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will
|
||||
* also fire for popup pages. See also {@link com.microsoft.playwright.Page#onPopup Page.onPopup()} to receive events about
|
||||
@@ -183,27 +141,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
*/
|
||||
void offPage(Consumer<Page> handler);
|
||||
|
||||
/**
|
||||
* Emitted when a page in this context is closed. See also {@link com.microsoft.playwright.Page#onClose Page.onClose()} to
|
||||
* receive events about a specific page.
|
||||
*/
|
||||
void onPageClose(Consumer<Page> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onPageClose onPageClose(handler)}.
|
||||
*/
|
||||
void offPageClose(Consumer<Page> handler);
|
||||
|
||||
/**
|
||||
* Emitted when the JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/Events/load">{@code load}</a> event is
|
||||
* dispatched in any page belonging to this context. See also {@link com.microsoft.playwright.Page#onLoad Page.onLoad()} to
|
||||
* receive events about a specific page.
|
||||
*/
|
||||
void onPageLoad(Consumer<Page> handler);
|
||||
/**
|
||||
* Removes handler that was previously added with {@link #onPageLoad onPageLoad(handler)}.
|
||||
*/
|
||||
void offPageLoad(Consumer<Page> handler);
|
||||
|
||||
/**
|
||||
* Emitted when exception is unhandled in any of the pages in this context. To listen for errors from a particular page,
|
||||
* use {@link com.microsoft.playwright.Page#onPageError Page.onPageError()} instead.
|
||||
@@ -334,6 +271,20 @@ public interface BrowserContext extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class ExposeBindingOptions {
|
||||
/**
|
||||
* @deprecated This option will be removed in the future.
|
||||
*/
|
||||
public Boolean handle;
|
||||
|
||||
/**
|
||||
* @deprecated This option will be removed in the future.
|
||||
*/
|
||||
public ExposeBindingOptions setHandle(boolean handle) {
|
||||
this.handle = handle;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class GrantPermissionsOptions {
|
||||
/**
|
||||
* The [origin] to grant permissions to, e.g. "https://example.com".
|
||||
@@ -563,13 +514,6 @@ 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.
|
||||
*
|
||||
@@ -792,7 +736,54 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @since v1.8
|
||||
*/
|
||||
AutoCloseable exposeBinding(String name, BindingCallback callback);
|
||||
default AutoCloseable exposeBinding(String name, BindingCallback callback) {
|
||||
return exposeBinding(name, callback, null);
|
||||
}
|
||||
/**
|
||||
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context.
|
||||
* When called, the function executes {@code callback} and returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
|
||||
* resolves to the return value of {@code callback}. If the {@code callback} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
|
||||
* awaited.
|
||||
*
|
||||
* <p> The first argument of the {@code callback} function contains information about the caller: {@code { browserContext:
|
||||
* BrowserContext, page: Page, frame: Frame }}.
|
||||
*
|
||||
* <p> See {@link com.microsoft.playwright.Page#exposeBinding Page.exposeBinding()} for page-only version.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
*
|
||||
* <p> An example of exposing page URL to all frames in all pages in the context:
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.*;
|
||||
*
|
||||
* public class Example {
|
||||
* public static void main(String[] args) {
|
||||
* try (Playwright playwright = Playwright.create()) {
|
||||
* BrowserType webkit = playwright.webkit();
|
||||
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
* BrowserContext context = browser.newContext();
|
||||
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
|
||||
* Page page = context.newPage();
|
||||
* page.setContent("<script>\n" +
|
||||
* " async function onClick() {\n" +
|
||||
* " document.querySelector('div').textContent = await window.pageURL();\n" +
|
||||
* " }\n" +
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.getByRole(AriaRole.BUTTON).click();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @since v1.8
|
||||
*/
|
||||
AutoCloseable exposeBinding(String name, BindingCallback callback, ExposeBindingOptions options);
|
||||
/**
|
||||
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context.
|
||||
* When called, the function executes {@code callback} and returns a <a
|
||||
|
||||
@@ -124,10 +124,6 @@ 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.
|
||||
*/
|
||||
@@ -137,15 +133,6 @@ public interface BrowserType {
|
||||
* the file system being the same between Playwright and the Browser.
|
||||
*/
|
||||
public Boolean isLocal;
|
||||
/**
|
||||
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
|
||||
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
|
||||
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
|
||||
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
|
||||
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
|
||||
* to {@code false}.
|
||||
*/
|
||||
public Boolean noDefaults;
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
* Defaults to 0.
|
||||
@@ -157,13 +144,6 @@ 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.
|
||||
*/
|
||||
@@ -179,18 +159,6 @@ public interface BrowserType {
|
||||
this.isLocal = isLocal;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
|
||||
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
|
||||
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
|
||||
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
|
||||
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
|
||||
* to {@code false}.
|
||||
*/
|
||||
public ConnectOverCDPOptions setNoDefaults(boolean noDefaults) {
|
||||
this.noDefaults = noDefaults;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
|
||||
* Defaults to 0.
|
||||
|
||||
@@ -17,12 +17,9 @@
|
||||
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;
|
||||
@@ -31,13 +28,7 @@ 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, 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;
|
||||
}
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
|
||||
ProcessBuilder pb = driver.createProcessBuilder();
|
||||
pb.command().addAll(asList(args));
|
||||
@@ -49,17 +40,4 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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 two 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 passkey, 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>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);
|
||||
}
|
||||
|
||||
@@ -867,13 +867,6 @@ public interface Frame {
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
|
||||
*/
|
||||
public Boolean checked;
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public Object description;
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -882,8 +875,8 @@ public interface Frame {
|
||||
*/
|
||||
public Boolean disabled;
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public Boolean exact;
|
||||
/**
|
||||
@@ -935,26 +928,6 @@ public interface Frame {
|
||||
this.checked = checked;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(Pattern description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -966,8 +939,8 @@ public interface Frame {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public GetByRoleOptions setExact(boolean exact) {
|
||||
this.exact = exact;
|
||||
@@ -2965,7 +2938,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.evaluateHandle("document.body");
|
||||
* ElementHandle bodyHandle = frame.evaluate("document.body");
|
||||
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -3005,7 +2978,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.evaluateHandle("document.body");
|
||||
* ElementHandle bodyHandle = frame.evaluate("document.body");
|
||||
* String html = (String) frame.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
|
||||
@@ -107,13 +107,6 @@ public interface FrameLocator {
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
|
||||
*/
|
||||
public Boolean checked;
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public Object description;
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -122,8 +115,8 @@ public interface FrameLocator {
|
||||
*/
|
||||
public Boolean disabled;
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public Boolean exact;
|
||||
/**
|
||||
@@ -175,26 +168,6 @@ public interface FrameLocator {
|
||||
this.checked = checked;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(Pattern description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -206,8 +179,8 @@ public interface FrameLocator {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public GetByRoleOptions setExact(boolean exact) {
|
||||
this.exact = exact;
|
||||
|
||||
@@ -30,13 +30,6 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public interface Locator {
|
||||
class AriaSnapshotOptions {
|
||||
/**
|
||||
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
|
||||
* are relative to the viewport, in CSS pixels, as returned by <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
|
||||
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean boxes;
|
||||
/**
|
||||
* When specified, limits the depth of the snapshot.
|
||||
*/
|
||||
@@ -54,16 +47,6 @@ public interface Locator {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
|
||||
* are relative to the viewport, in CSS pixels, as returned by <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
|
||||
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
|
||||
*/
|
||||
public AriaSnapshotOptions setBoxes(boolean boxes) {
|
||||
this.boxes = boxes;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When specified, limits the depth of the snapshot.
|
||||
*/
|
||||
@@ -662,46 +645,6 @@ public interface Locator {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class DropOptions {
|
||||
/**
|
||||
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
|
||||
* element.
|
||||
*/
|
||||
public Position position;
|
||||
/**
|
||||
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
|
||||
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
|
||||
* methods.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
|
||||
* element.
|
||||
*/
|
||||
public DropOptions setPosition(double x, double y) {
|
||||
return setPosition(new Position(x, y));
|
||||
}
|
||||
/**
|
||||
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
|
||||
* element.
|
||||
*/
|
||||
public DropOptions setPosition(Position position) {
|
||||
this.position = position;
|
||||
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
|
||||
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
|
||||
* methods.
|
||||
*/
|
||||
public DropOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class ElementHandleOptions {
|
||||
/**
|
||||
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
|
||||
@@ -1000,13 +943,6 @@ public interface Locator {
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
|
||||
*/
|
||||
public Boolean checked;
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public Object description;
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -1015,8 +951,8 @@ public interface Locator {
|
||||
*/
|
||||
public Boolean disabled;
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public Boolean exact;
|
||||
/**
|
||||
@@ -1068,26 +1004,6 @@ public interface Locator {
|
||||
this.checked = checked;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(Pattern description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -1099,8 +1015,8 @@ public interface Locator {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public GetByRoleOptions setExact(boolean exact) {
|
||||
this.exact = exact;
|
||||
@@ -1206,20 +1122,6 @@ public interface Locator {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class HighlightOptions {
|
||||
/**
|
||||
* Additional inline CSS applied to the highlight overlay, e.g. {@code "outline: 2px dashed red"}.
|
||||
*/
|
||||
public String style;
|
||||
|
||||
/**
|
||||
* Additional inline CSS applied to the highlight overlay, e.g. {@code "outline: 2px dashed red"}.
|
||||
*/
|
||||
public HighlightOptions setStyle(String style) {
|
||||
this.style = style;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class HoverOptions {
|
||||
/**
|
||||
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks. Defaults to
|
||||
@@ -2998,54 +2900,6 @@ public interface Locator {
|
||||
* @since v1.18
|
||||
*/
|
||||
void dragTo(Locator target, DragToOptions options);
|
||||
/**
|
||||
* Simulate an external drag-and-drop of files or clipboard-like data onto this locator.
|
||||
*
|
||||
* <p> <strong>Details</strong>
|
||||
*
|
||||
* <p> Dispatches the native {@code dragenter}, {@code dragover}, and {@code drop} events at the center of the target element
|
||||
* with a synthetic [DataTransfer] carrying the provided files and/or data entries. Works cross-browser by constructing the
|
||||
* [DataTransfer] in the page context.
|
||||
*
|
||||
* <p> If the target element's {@code dragover} listener does not call {@code preventDefault()}, the target is considered to
|
||||
* have rejected the drop: Playwright dispatches {@code dragleave} and this method throws.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
*
|
||||
* <p> Drop a file buffer onto an upload area:
|
||||
*
|
||||
* <p> Drop plain text and a URL together:
|
||||
*
|
||||
* @param payload Data to drop onto the target. Provide {@code files} (file paths or in-memory buffers), {@code data} (a mime-type →
|
||||
* string map for clipboard-like content such as {@code text/plain}, {@code text/html}, {@code text/uri-list}), or both.
|
||||
* @since v1.60
|
||||
*/
|
||||
default void drop(DropPayload payload) {
|
||||
drop(payload, null);
|
||||
}
|
||||
/**
|
||||
* Simulate an external drag-and-drop of files or clipboard-like data onto this locator.
|
||||
*
|
||||
* <p> <strong>Details</strong>
|
||||
*
|
||||
* <p> Dispatches the native {@code dragenter}, {@code dragover}, and {@code drop} events at the center of the target element
|
||||
* with a synthetic [DataTransfer] carrying the provided files and/or data entries. Works cross-browser by constructing the
|
||||
* [DataTransfer] in the page context.
|
||||
*
|
||||
* <p> If the target element's {@code dragover} listener does not call {@code preventDefault()}, the target is considered to
|
||||
* have rejected the drop: Playwright dispatches {@code dragleave} and this method throws.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
*
|
||||
* <p> Drop a file buffer onto an upload area:
|
||||
*
|
||||
* <p> Drop plain text and a URL together:
|
||||
*
|
||||
* @param payload Data to drop onto the target. Provide {@code files} (file paths or in-memory buffers), {@code data} (a mime-type →
|
||||
* string map for clipboard-like content such as {@code text/plain}, {@code text/html}, {@code text/uri-list}), or both.
|
||||
* @since v1.60
|
||||
*/
|
||||
void drop(DropPayload payload, DropOptions options);
|
||||
/**
|
||||
* Resolves given locator to the first matching DOM element. If there are no matching elements, waits for one. If multiple
|
||||
* elements match the locator, throws.
|
||||
@@ -4025,28 +3879,13 @@ public interface Locator {
|
||||
* @since v1.27
|
||||
*/
|
||||
Locator getByTitle(Pattern text, GetByTitleOptions options);
|
||||
/**
|
||||
* Hides the element highlight previously added by {@link com.microsoft.playwright.Locator#highlight Locator.highlight()}.
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
void hideHighlight();
|
||||
/**
|
||||
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses {@link
|
||||
* com.microsoft.playwright.Locator#highlight Locator.highlight()}.
|
||||
*
|
||||
* @since v1.20
|
||||
*/
|
||||
default AutoCloseable highlight() {
|
||||
return highlight(null);
|
||||
}
|
||||
/**
|
||||
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses {@link
|
||||
* com.microsoft.playwright.Locator#highlight Locator.highlight()}.
|
||||
*
|
||||
* @since v1.20
|
||||
*/
|
||||
AutoCloseable highlight(HighlightOptions options);
|
||||
void highlight();
|
||||
/**
|
||||
* Hover over the matching element.
|
||||
*
|
||||
|
||||
@@ -1058,6 +1058,20 @@ public interface Page extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class ExposeBindingOptions {
|
||||
/**
|
||||
* @deprecated This option will be removed in the future.
|
||||
*/
|
||||
public Boolean handle;
|
||||
|
||||
/**
|
||||
* @deprecated This option will be removed in the future.
|
||||
*/
|
||||
public ExposeBindingOptions setHandle(boolean handle) {
|
||||
this.handle = handle;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class FillOptions {
|
||||
/**
|
||||
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks. Defaults to
|
||||
@@ -1236,13 +1250,6 @@ public interface Page extends AutoCloseable {
|
||||
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
|
||||
*/
|
||||
public Boolean checked;
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public Object description;
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -1251,8 +1258,8 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
public Boolean disabled;
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public Boolean exact;
|
||||
/**
|
||||
@@ -1304,26 +1311,6 @@ public interface Page extends AutoCloseable {
|
||||
this.checked = checked;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
|
||||
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
|
||||
*
|
||||
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
|
||||
*/
|
||||
public GetByRoleOptions setDescription(Pattern description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
|
||||
*
|
||||
@@ -1335,8 +1322,8 @@ public interface Page extends AutoCloseable {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
|
||||
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
|
||||
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
|
||||
* is a regular expression. Note that exact match still trims whitespace.
|
||||
*/
|
||||
public GetByRoleOptions setExact(boolean exact) {
|
||||
this.exact = exact;
|
||||
@@ -2994,13 +2981,6 @@ public interface Page extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
class AriaSnapshotOptions {
|
||||
/**
|
||||
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
|
||||
* are relative to the viewport, in CSS pixels, as returned by <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
|
||||
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
|
||||
*/
|
||||
public Boolean boxes;
|
||||
/**
|
||||
* When specified, limits the depth of the snapshot.
|
||||
*/
|
||||
@@ -3018,16 +2998,6 @@ public interface Page extends AutoCloseable {
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
|
||||
* are relative to the viewport, in CSS pixels, as returned by <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
|
||||
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
|
||||
*/
|
||||
public AriaSnapshotOptions setBoxes(boolean boxes) {
|
||||
this.boxes = boxes;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When specified, limits the depth of the snapshot.
|
||||
*/
|
||||
@@ -4529,7 +4499,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.evaluateHandle("document.body");
|
||||
* ElementHandle bodyHandle = page.evaluate("document.body");
|
||||
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -4571,7 +4541,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.evaluateHandle("document.body");
|
||||
* ElementHandle bodyHandle = page.evaluate("document.body");
|
||||
* String html = (String) page.evaluate("([body, suffix]) => body.innerHTML + suffix", Arrays.asList(bodyHandle, "hello"));
|
||||
* bodyHandle.dispose();
|
||||
* }</pre>
|
||||
@@ -4706,7 +4676,57 @@ public interface Page extends AutoCloseable {
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @since v1.8
|
||||
*/
|
||||
AutoCloseable exposeBinding(String name, BindingCallback callback);
|
||||
default AutoCloseable exposeBinding(String name, BindingCallback callback) {
|
||||
return exposeBinding(name, callback, null);
|
||||
}
|
||||
/**
|
||||
* The method adds a function called {@code name} on the {@code window} object of every frame in this page. When called,
|
||||
* the function executes {@code callback} and returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
|
||||
* resolves to the return value of {@code callback}. If the {@code callback} returns a <a
|
||||
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
|
||||
* awaited.
|
||||
*
|
||||
* <p> The first argument of the {@code callback} function contains information about the caller: {@code { browserContext:
|
||||
* BrowserContext, page: Page, frame: Frame }}.
|
||||
*
|
||||
* <p> See {@link com.microsoft.playwright.BrowserContext#exposeBinding BrowserContext.exposeBinding()} for the context-wide
|
||||
* version.
|
||||
*
|
||||
* <p> <strong>NOTE:</strong> Functions installed via {@link com.microsoft.playwright.Page#exposeBinding Page.exposeBinding()} survive navigations.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
*
|
||||
* <p> An example of exposing page URL to all frames in a page:
|
||||
* <pre>{@code
|
||||
* import com.microsoft.playwright.*;
|
||||
*
|
||||
* public class Example {
|
||||
* public static void main(String[] args) {
|
||||
* try (Playwright playwright = Playwright.create()) {
|
||||
* BrowserType webkit = playwright.webkit();
|
||||
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
* BrowserContext context = browser.newContext();
|
||||
* Page page = context.newPage();
|
||||
* page.exposeBinding("pageURL", (source, args) -> source.page().url());
|
||||
* page.setContent("<script>\n" +
|
||||
* " async function onClick() {\n" +
|
||||
* " document.querySelector('div').textContent = await window.pageURL();\n" +
|
||||
* " }\n" +
|
||||
* "</script>\n" +
|
||||
* "<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
* "<div></div>");
|
||||
* page.click("button");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @since v1.8
|
||||
*/
|
||||
AutoCloseable exposeBinding(String name, BindingCallback callback, ExposeBindingOptions options);
|
||||
/**
|
||||
* The method adds a function called {@code name} on the {@code window} object of every frame in the page. When called, the
|
||||
* function executes {@code callback} and returns a <a
|
||||
@@ -5577,13 +5597,6 @@ public interface Page extends AutoCloseable {
|
||||
* @since v1.8
|
||||
*/
|
||||
Response navigate(String url, NavigateOptions options);
|
||||
/**
|
||||
* Hide all locator highlight overlays previously added by {@link com.microsoft.playwright.Locator#highlight
|
||||
* Locator.highlight()} on this page.
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
void hideHighlight();
|
||||
/**
|
||||
* This method hovers over an element matching {@code selector} by performing the following steps:
|
||||
* <ol>
|
||||
@@ -5808,18 +5821,6 @@ 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.
|
||||
@@ -7564,8 +7565,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()} will throw if the {@code hasTouch} option of the browser context is
|
||||
* false.
|
||||
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} the method will throw if {@code hasTouch} option of the browser
|
||||
* context is false.
|
||||
*
|
||||
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
|
||||
* @since v1.8
|
||||
@@ -7587,8 +7588,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()} will throw if the {@code hasTouch} option of the browser context is
|
||||
* false.
|
||||
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} the method will throw if {@code hasTouch} option of the browser
|
||||
* context is false.
|
||||
*
|
||||
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
|
||||
* @since v1.8
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.function.Consumer;
|
||||
public interface Screencast {
|
||||
class StartOptions {
|
||||
/**
|
||||
* Callback that receives JPEG-encoded frame data along with the page viewport size at the time of capture.
|
||||
* Callback that receives JPEG-encoded frame data.
|
||||
*/
|
||||
public Consumer<ScreencastFrame> onFrame;
|
||||
/**
|
||||
@@ -38,16 +38,9 @@ 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.
|
||||
* Callback that receives JPEG-encoded frame data.
|
||||
*/
|
||||
public StartOptions setOnFrame(Consumer<ScreencastFrame> onFrame) {
|
||||
this.onFrame = onFrame;
|
||||
@@ -67,25 +60,6 @@ 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 {
|
||||
/**
|
||||
@@ -129,11 +103,6 @@ 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}.
|
||||
*/
|
||||
@@ -147,14 +116,6 @@ 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}.
|
||||
*/
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
public interface ScreencastFrame {
|
||||
/**
|
||||
* JPEG-encoded frame data.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
int viewportWidth();
|
||||
|
||||
/**
|
||||
* Height of the page viewport at the time the frame was captured.
|
||||
*/
|
||||
int viewportHeight();
|
||||
}
|
||||
@@ -201,8 +201,7 @@ 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. To match elements with any of several attributes, pass them as a comma-separated list, e.g.
|
||||
* {@code "data-pw,data-ti"}.
|
||||
* @param attributeName Test id attribute name.
|
||||
* @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.Touchscreen#tap Touchscreen.tap()} will throw if the {@code hasTouch} option of the
|
||||
* browser context is false.
|
||||
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} the method will throw if {@code hasTouch} option of the browser
|
||||
* context is false.
|
||||
*
|
||||
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
|
||||
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
|
||||
@@ -166,59 +165,6 @@ public interface Tracing {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class StartHarOptions {
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
|
||||
* {@code attach} is specified, resources are persisted as separate files or entries in the ZIP archive. If {@code embed}
|
||||
* is specified, content is stored inline the HAR file as per HAR specification. Defaults to {@code attach} for {@code
|
||||
* .zip} output files and to {@code embed} for all other file extensions.
|
||||
*/
|
||||
public HarContentPolicy content;
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
|
||||
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
|
||||
* full}.
|
||||
*/
|
||||
public HarMode mode;
|
||||
/**
|
||||
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
|
||||
*/
|
||||
public Object urlFilter;
|
||||
|
||||
/**
|
||||
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
|
||||
* {@code attach} is specified, resources are persisted as separate files or entries in the ZIP archive. If {@code embed}
|
||||
* is specified, content is stored inline the HAR file as per HAR specification. Defaults to {@code attach} for {@code
|
||||
* .zip} output files and to {@code embed} for all other file extensions.
|
||||
*/
|
||||
public StartHarOptions setContent(HarContentPolicy content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
|
||||
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
|
||||
* full}.
|
||||
*/
|
||||
public StartHarOptions setMode(HarMode mode) {
|
||||
this.mode = mode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
|
||||
*/
|
||||
public StartHarOptions setUrlFilter(String urlFilter) {
|
||||
this.urlFilter = urlFilter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
|
||||
*/
|
||||
public StartHarOptions setUrlFilter(Pattern urlFilter) {
|
||||
this.urlFilter = urlFilter;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class GroupOptions {
|
||||
/**
|
||||
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
|
||||
@@ -382,48 +328,6 @@ public interface Tracing {
|
||||
* @since v1.15
|
||||
*/
|
||||
void startChunk(StartChunkOptions options);
|
||||
/**
|
||||
* Start recording a HAR (HTTP Archive) of network activity in this context. The HAR file is written to disk when {@link
|
||||
* com.microsoft.playwright.Tracing#stopHar Tracing.stopHar()} is called, or when the returned {@code Disposable} is
|
||||
* disposed.
|
||||
*
|
||||
* <p> Only one HAR recording can be active at a time per {@code BrowserContext}.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* context.tracing().startHar(Paths.get("trace.har"));
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://playwright.dev");
|
||||
* context.tracing().stopHar();
|
||||
* }</pre>
|
||||
*
|
||||
* @param path Path on the filesystem to write the HAR file to. If the file name ends with {@code .zip}, the HAR is saved as a zip
|
||||
* archive with response bodies attached as separate files.
|
||||
* @since v1.60
|
||||
*/
|
||||
default AutoCloseable startHar(Path path) {
|
||||
return startHar(path, null);
|
||||
}
|
||||
/**
|
||||
* Start recording a HAR (HTTP Archive) of network activity in this context. The HAR file is written to disk when {@link
|
||||
* com.microsoft.playwright.Tracing#stopHar Tracing.stopHar()} is called, or when the returned {@code Disposable} is
|
||||
* disposed.
|
||||
*
|
||||
* <p> Only one HAR recording can be active at a time per {@code BrowserContext}.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* context.tracing().startHar(Paths.get("trace.har"));
|
||||
* Page page = context.newPage();
|
||||
* page.navigate("https://playwright.dev");
|
||||
* context.tracing().stopHar();
|
||||
* }</pre>
|
||||
*
|
||||
* @param path Path on the filesystem to write the HAR file to. If the file name ends with {@code .zip}, the HAR is saved as a zip
|
||||
* archive with response bodies attached as separate files.
|
||||
* @since v1.60
|
||||
*/
|
||||
AutoCloseable startHar(Path path, StartHarOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
|
||||
*
|
||||
@@ -504,12 +408,5 @@ public interface Tracing {
|
||||
* @since v1.15
|
||||
*/
|
||||
void stopChunk(StopChunkOptions options);
|
||||
/**
|
||||
* Stop HAR recording and save the HAR file to the path given to {@link com.microsoft.playwright.Tracing#startHar
|
||||
* Tracing.startHar()}.
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
void stopHar();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
/**
|
||||
* {@code WebError} class represents an unhandled exception thrown in the page. It is dispatched via the {@link
|
||||
@@ -44,11 +43,5 @@ public interface WebError {
|
||||
* @since v1.38
|
||||
*/
|
||||
String error();
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
WebErrorLocation location();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -214,27 +213,6 @@ public interface WebSocketRoute {
|
||||
* @since v1.48
|
||||
*/
|
||||
void send(byte[] message);
|
||||
/**
|
||||
* The list of WebSocket subprotocols requested by the page, as passed via the second argument to the <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket">{@code WebSocket} constructor</a>.
|
||||
* Corresponds to the {@code Sec-WebSocket-Protocol} request header.
|
||||
*
|
||||
* <p> Returns an empty array if no protocols were specified.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* page.routeWebSocket("wss://example.com/ws", ws -> {
|
||||
* if (ws.protocols().contains("chat.v2")) {
|
||||
* ws.onMessage(frame -> ws.send("v2:" + frame.text()));
|
||||
* } else {
|
||||
* ws.close(1002, "Unsupported protocol");
|
||||
* }
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
List<String> protocols();
|
||||
/**
|
||||
* URL of the WebSocket created in the page.
|
||||
*
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.microsoft.playwright.assertions;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import com.microsoft.playwright.options.AriaRole;
|
||||
import com.microsoft.playwright.options.PseudoElement;
|
||||
|
||||
/**
|
||||
* The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code
|
||||
@@ -428,22 +427,11 @@ public interface LocatorAssertions {
|
||||
}
|
||||
}
|
||||
class HasCSSOptions {
|
||||
/**
|
||||
* Pseudo-element to read computed styles from.
|
||||
*/
|
||||
public PseudoElement pseudo;
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Pseudo-element to read computed styles from.
|
||||
*/
|
||||
public HasCSSOptions setPseudo(PseudoElement pseudo) {
|
||||
this.pseudo = pseudo;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
|
||||
*/
|
||||
|
||||
@@ -37,20 +37,6 @@ import java.util.regex.Pattern;
|
||||
* }</pre>
|
||||
*/
|
||||
public interface PageAssertions {
|
||||
class MatchesAriaSnapshotOptions {
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
|
||||
*/
|
||||
public MatchesAriaSnapshotOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class HasTitleOptions {
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
|
||||
@@ -105,40 +91,6 @@ public interface PageAssertions {
|
||||
* @since v1.20
|
||||
*/
|
||||
PageAssertions not();
|
||||
/**
|
||||
* Asserts that the page body matches the given <a href="https://playwright.dev/java/docs/aria-snapshots">accessibility
|
||||
* snapshot</a>.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* page.navigate("https://demo.playwright.dev/todomvc/");
|
||||
* assertThat(page).matchesAriaSnapshot("""
|
||||
* - heading "todos"
|
||||
* - textbox "What needs to be done?"
|
||||
* """);
|
||||
* }</pre>
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
default void matchesAriaSnapshot(String expected) {
|
||||
matchesAriaSnapshot(expected, null);
|
||||
}
|
||||
/**
|
||||
* Asserts that the page body matches the given <a href="https://playwright.dev/java/docs/aria-snapshots">accessibility
|
||||
* snapshot</a>.
|
||||
*
|
||||
* <p> <strong>Usage</strong>
|
||||
* <pre>{@code
|
||||
* page.navigate("https://demo.playwright.dev/todomvc/");
|
||||
* assertThat(page).matchesAriaSnapshot("""
|
||||
* - heading "todos"
|
||||
* - textbox "What needs to be done?"
|
||||
* """);
|
||||
* }</pre>
|
||||
*
|
||||
* @since v1.60
|
||||
*/
|
||||
void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions options);
|
||||
/**
|
||||
* Ensures the page has the given title.
|
||||
*
|
||||
|
||||
@@ -46,11 +46,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
|
||||
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.microsoft.playwright.Tracing tracing() {
|
||||
return tracing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIResponse delete(String url, RequestOptions options) {
|
||||
return fetch(url, ensureOptions(options, "DELETE"));
|
||||
|
||||
@@ -22,8 +22,6 @@ 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 java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
@@ -87,22 +85,6 @@ 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();
|
||||
|
||||
@@ -57,14 +57,7 @@ abstract class AssertionsBase {
|
||||
}
|
||||
FrameExpectResult result = doExpect(expression, expectOptions, title);
|
||||
if (result.matches == isNot) {
|
||||
Object actual;
|
||||
if (result.received == null) {
|
||||
actual = null;
|
||||
} else if (result.received.value != null) {
|
||||
actual = Serialization.deserialize(result.received.value);
|
||||
} else {
|
||||
actual = result.received.ariaSnapshot;
|
||||
}
|
||||
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
|
||||
String log = (result.log == null) ? "" : String.join("\n", result.log);
|
||||
if (!log.isEmpty()) {
|
||||
log = "\nCall log:\n" + log;
|
||||
|
||||
@@ -46,7 +46,6 @@ 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();
|
||||
@@ -69,18 +68,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
|
||||
final TimeoutSettings timeoutSettings = new TimeoutSettings();
|
||||
final Map<String, HarRecorder> harRecorders = new HashMap<>();
|
||||
|
||||
static class HarRecorder {
|
||||
final Path path;
|
||||
final HarContentPolicy contentPolicy;
|
||||
|
||||
HarRecorder(Path har, HarContentPolicy policy) {
|
||||
path = har;
|
||||
contentPolicy = policy;
|
||||
}
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
CONSOLE,
|
||||
DIALOG,
|
||||
DOWNLOAD,
|
||||
FRAMEATTACHED,
|
||||
FRAMEDETACHED,
|
||||
FRAMENAVIGATED,
|
||||
PAGE,
|
||||
PAGECLOSE,
|
||||
PAGELOAD,
|
||||
WEBERROR,
|
||||
REQUEST,
|
||||
REQUESTFAILED,
|
||||
@@ -95,7 +99,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -136,20 +139,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
public void offBackgroundPage(Consumer<Page> handler) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(Consumer<Download> handler) {
|
||||
listeners.add(EventType.DOWNLOAD, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offDownload(Consumer<Download> handler) {
|
||||
listeners.remove(EventType.DOWNLOAD, handler);
|
||||
}
|
||||
|
||||
void notifyDownload(Download download) {
|
||||
listeners.notify(EventType.DOWNLOAD, download);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Consumer<BrowserContext> handler) {
|
||||
listeners.add(EventType.CLOSE, handler);
|
||||
@@ -190,76 +179,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
listeners.remove(EventType.PAGE, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameAttached(Consumer<Frame> handler) {
|
||||
listeners.add(EventType.FRAMEATTACHED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offFrameAttached(Consumer<Frame> handler) {
|
||||
listeners.remove(EventType.FRAMEATTACHED, handler);
|
||||
}
|
||||
|
||||
void notifyFrameAttached(FrameImpl frame) {
|
||||
listeners.notify(EventType.FRAMEATTACHED, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameDetached(Consumer<Frame> handler) {
|
||||
listeners.add(EventType.FRAMEDETACHED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offFrameDetached(Consumer<Frame> handler) {
|
||||
listeners.remove(EventType.FRAMEDETACHED, handler);
|
||||
}
|
||||
|
||||
void notifyFrameDetached(FrameImpl frame) {
|
||||
listeners.notify(EventType.FRAMEDETACHED, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameNavigated(Consumer<Frame> handler) {
|
||||
listeners.add(EventType.FRAMENAVIGATED, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offFrameNavigated(Consumer<Frame> handler) {
|
||||
listeners.remove(EventType.FRAMENAVIGATED, handler);
|
||||
}
|
||||
|
||||
void notifyFrameNavigated(FrameImpl frame) {
|
||||
listeners.notify(EventType.FRAMENAVIGATED, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageClose(Consumer<Page> handler) {
|
||||
listeners.add(EventType.PAGECLOSE, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offPageClose(Consumer<Page> handler) {
|
||||
listeners.remove(EventType.PAGECLOSE, handler);
|
||||
}
|
||||
|
||||
void notifyPageClose(PageImpl page) {
|
||||
listeners.notify(EventType.PAGECLOSE, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageLoad(Consumer<Page> handler) {
|
||||
listeners.add(EventType.PAGELOAD, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offPageLoad(Consumer<Page> handler) {
|
||||
listeners.remove(EventType.PAGELOAD, handler);
|
||||
}
|
||||
|
||||
void notifyPageLoad(PageImpl page) {
|
||||
listeners.notify(EventType.PAGELOAD, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebError(Consumer<WebError> handler) {
|
||||
listeners.add(EventType.WEBERROR, handler);
|
||||
@@ -315,11 +234,6 @@ 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));
|
||||
@@ -370,7 +284,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
closeReason = options.reason;
|
||||
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
|
||||
tracing.exportAllHars();
|
||||
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", entry.getKey());
|
||||
JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
|
||||
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
// Server side will compress artifact if content is attach or if file is .zip.
|
||||
HarRecorder harParams = entry.getValue();
|
||||
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
|
||||
boolean needCompressed = harParams.path.toString().endsWith(".zip");
|
||||
if (isCompressed && !needCompressed) {
|
||||
String tmpPath = harParams.path + ".tmp";
|
||||
artifact.saveAs(Paths.get(tmpPath));
|
||||
JsonObject unzipParams = new JsonObject();
|
||||
unzipParams.addProperty("zipFile", tmpPath);
|
||||
unzipParams.addProperty("harFile", harParams.path.toString());
|
||||
connection.localUtils.sendMessage("harUnzip", unzipParams, NO_TIMEOUT);
|
||||
} else {
|
||||
artifact.saveAs(harParams.path);
|
||||
}
|
||||
artifact.delete();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
sendMessage("close", params, NO_TIMEOUT);
|
||||
}
|
||||
@@ -458,11 +392,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoCloseable exposeBinding(String name, BindingCallback playwrightBinding) {
|
||||
return exposeBindingImpl(name, playwrightBinding);
|
||||
public AutoCloseable exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
return exposeBindingImpl(name, playwrightBinding, options);
|
||||
}
|
||||
|
||||
private AutoCloseable exposeBindingImpl(String name, BindingCallback playwrightBinding) {
|
||||
private AutoCloseable exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
if (bindings.containsKey(name)) {
|
||||
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
|
||||
}
|
||||
@@ -475,13 +409,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("name", name);
|
||||
if (options != null && options.handle != null && options.handle) {
|
||||
params.addProperty("needsHandle", true);
|
||||
}
|
||||
JsonObject result = sendMessage("exposeBinding", params, NO_TIMEOUT).getAsJsonObject();
|
||||
return connection.getExistingObject(result.getAsJsonObject("disposable").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoCloseable exposeFunction(String name, FunctionCallback playwrightFunction) {
|
||||
return exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args));
|
||||
return exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -578,7 +515,24 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
if (contentPolicy == null) {
|
||||
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);
|
||||
}
|
||||
tracing.recordIntoHar(page, har, options.url, contentPolicy, options.updateMode, null);
|
||||
if (contentPolicy == null) {
|
||||
contentPolicy = HarContentPolicy.ATTACH;
|
||||
}
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
if (page != null) {
|
||||
params.add("page", page.toProtocolRef());
|
||||
}
|
||||
JsonObject recordHarArgs = new JsonObject();
|
||||
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
|
||||
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
|
||||
recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
|
||||
addHarUrlFilter(recordHarArgs, options.url);
|
||||
|
||||
params.add("options", recordHarArgs);
|
||||
JsonObject json = sendMessage("harStart", params, NO_TIMEOUT).getAsJsonObject();
|
||||
String harId = json.get("harId").getAsString();
|
||||
harRecorders.put(harId, new HarRecorder(har, contentPolicy));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -864,11 +818,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
} catch (PlaywrightException e) {
|
||||
page = null;
|
||||
}
|
||||
WebErrorLocation location = null;
|
||||
if (params.has("location")) {
|
||||
location = gson().fromJson(params.getAsJsonObject("location"), WebErrorLocation.class);
|
||||
}
|
||||
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr, location));
|
||||
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr));
|
||||
if (page != null) {
|
||||
page.listeners.notify(PageImpl.EventType.PAGEERROR, errorStr);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
String closeReason;
|
||||
|
||||
enum EventType {
|
||||
CONTEXT,
|
||||
DISCONNECTED,
|
||||
}
|
||||
|
||||
@@ -51,16 +50,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContext(Consumer<BrowserContext> handler) {
|
||||
listeners.add(EventType.CONTEXT, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offContext(Consumer<BrowserContext> handler) {
|
||||
listeners.remove(EventType.CONTEXT, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(Consumer<Browser> handler) {
|
||||
listeners.add(EventType.DISCONNECTED, handler);
|
||||
@@ -313,7 +302,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
context.tracing().setTracesDir(tracePath);
|
||||
browserType.playwright.selectors.contextsForSelectors.add(context);
|
||||
}
|
||||
listeners.notify(EventType.CONTEXT, context);
|
||||
}
|
||||
|
||||
private void didClose() {
|
||||
|
||||
@@ -112,8 +112,8 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
|
||||
@Override
|
||||
public Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options) {
|
||||
if (!"chromium".equals(name()) && !"webkit".equals(name())) {
|
||||
throw new PlaywrightException("Connecting over CDP is only supported in Chromium and WebKit.");
|
||||
if (!"chromium".equals(name())) {
|
||||
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
|
||||
}
|
||||
if (options == null) {
|
||||
options = new ConnectOverCDPOptions();
|
||||
|
||||
@@ -110,14 +110,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ class Message {
|
||||
JsonObject params;
|
||||
JsonElement result;
|
||||
SerializedError error;
|
||||
JsonObject errorDetails;
|
||||
JsonArray log;
|
||||
|
||||
@Override
|
||||
@@ -133,20 +132,13 @@ public class Connection {
|
||||
}
|
||||
|
||||
public WaitableResult<JsonElement> sendMessageAsync(String guid, String method, JsonObject params) {
|
||||
return internalSendMessage(guid, method, params, true, true);
|
||||
return internalSendMessage(guid, method, params, true);
|
||||
}
|
||||
|
||||
// Fire-and-forget: the server never replies.
|
||||
public void sendMessageNoReply(String guid, String method, JsonObject params) {
|
||||
internalSendMessage(guid, method, params, false, false);
|
||||
}
|
||||
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params, boolean sendStack, boolean expectsReply) {
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params, boolean sendStack) {
|
||||
int id = ++lastId;
|
||||
WaitableResult<JsonElement> result = new WaitableResult<>();
|
||||
if (expectsReply) {
|
||||
callbacks.put(id, result);
|
||||
}
|
||||
callbacks.put(id, result);
|
||||
JsonObject message = new JsonObject();
|
||||
message.addProperty("id", id);
|
||||
message.addProperty("guid", guid);
|
||||
@@ -183,7 +175,7 @@ public class Connection {
|
||||
callData.add("stack", stack);
|
||||
JsonObject stackParams = new JsonObject();
|
||||
stackParams.add("callData", callData);
|
||||
internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, false, true);
|
||||
internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -259,20 +251,16 @@ public class Connection {
|
||||
callback.complete(message.result);
|
||||
} else {
|
||||
String callLog = formatCallLog(message.log);
|
||||
PlaywrightException exception;
|
||||
if (message.error.error == null) {
|
||||
exception = new PlaywrightException(message.error + callLog);
|
||||
callback.completeExceptionally(new PlaywrightException(message.error + callLog));
|
||||
} else if ("TimeoutError".equals(message.error.error.name)) {
|
||||
exception = new TimeoutError(message.error.error + callLog);
|
||||
callback.completeExceptionally(new TimeoutError(message.error.error + callLog));
|
||||
} else if ("TargetClosedError".equals(message.error.error.name)) {
|
||||
exception = new TargetClosedError(message.error.error + callLog);
|
||||
callback.completeExceptionally(new TargetClosedError(message.error.error + callLog));
|
||||
|
||||
} else {
|
||||
exception = new DriverException(message.error.error + callLog);
|
||||
callback.completeExceptionally(new DriverException(message.error.error + callLog));
|
||||
}
|
||||
if (message.errorDetails != null) {
|
||||
exception = new ServerErrorWithDetails(exception, message.errorDetails, message.log);
|
||||
}
|
||||
callback.completeExceptionally(exception);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.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));
|
||||
}
|
||||
}
|
||||
@@ -1051,58 +1051,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return result.get("value").getAsInt();
|
||||
}
|
||||
|
||||
void dropImpl(String selector, DropPayload payload, com.microsoft.playwright.Locator.DropOptions options) {
|
||||
if (options == null) {
|
||||
options = new com.microsoft.playwright.Locator.DropOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
params.addProperty("strict", true);
|
||||
if (payload != null) {
|
||||
if (payload.files != null) {
|
||||
if (payload.files instanceof Path) {
|
||||
addFilePathUploadParams(new Path[] { (Path) payload.files }, params, page.context());
|
||||
} else if (payload.files instanceof Path[]) {
|
||||
addFilePathUploadParams((Path[]) payload.files, params, page.context());
|
||||
} else if (payload.files instanceof com.microsoft.playwright.options.FilePayload) {
|
||||
checkFilePayloadSize(new com.microsoft.playwright.options.FilePayload[] { (com.microsoft.playwright.options.FilePayload) payload.files });
|
||||
params.add("payloads", toJsonArray(new com.microsoft.playwright.options.FilePayload[] { (com.microsoft.playwright.options.FilePayload) payload.files }));
|
||||
} else if (payload.files instanceof com.microsoft.playwright.options.FilePayload[]) {
|
||||
checkFilePayloadSize((com.microsoft.playwright.options.FilePayload[]) payload.files);
|
||||
params.add("payloads", toJsonArray((com.microsoft.playwright.options.FilePayload[]) payload.files));
|
||||
} else {
|
||||
throw new com.microsoft.playwright.PlaywrightException("Unsupported files type: " + payload.files.getClass());
|
||||
}
|
||||
}
|
||||
if (payload.data != null) {
|
||||
com.google.gson.JsonArray dataArray = new com.google.gson.JsonArray();
|
||||
for (java.util.Map.Entry<String, String> entry : payload.data.entrySet()) {
|
||||
JsonObject e = new JsonObject();
|
||||
e.addProperty("mimeType", entry.getKey());
|
||||
e.addProperty("value", entry.getValue());
|
||||
dataArray.add(e);
|
||||
}
|
||||
params.add("data", dataArray);
|
||||
}
|
||||
}
|
||||
sendMessage("drop", params, timeout(options.timeout));
|
||||
}
|
||||
|
||||
void highlightImpl(String selector, String style) {
|
||||
void highlightImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
if (style != null) {
|
||||
params.addProperty("style", style);
|
||||
}
|
||||
sendMessage("highlight", params, NO_TIMEOUT);
|
||||
}
|
||||
|
||||
void hideHighlightImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("hideHighlight", params, NO_TIMEOUT);
|
||||
}
|
||||
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("loadstate".equals(event)) {
|
||||
JsonElement add = params.get("add");
|
||||
@@ -1112,7 +1066,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
if (parentFrame == null && page != null) {
|
||||
if (state == LOAD) {
|
||||
page.listeners.notify(PageImpl.EventType.LOAD, page);
|
||||
page.browserContext.notifyPageLoad(page);
|
||||
} else if (state == DOMCONTENTLOADED) {
|
||||
page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page);
|
||||
}
|
||||
@@ -1154,17 +1107,8 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
FrameExpectResult expect(String expression, FrameExpectOptions options) {
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("expression", expression);
|
||||
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();
|
||||
}
|
||||
JsonElement json = sendMessage("expect", params, options.timeout);
|
||||
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,20 +371,8 @@ class LocatorImpl implements Locator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drop(DropPayload payload, DropOptions options) {
|
||||
frame.dropImpl(selector, payload, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoCloseable highlight(HighlightOptions options) {
|
||||
String style = options == null ? null : options.style;
|
||||
frame.highlightImpl(selector, style);
|
||||
return new DisposableStub(this::hideHighlight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideHighlight() {
|
||||
frame.hideHighlightImpl(selector);
|
||||
public void highlight() {
|
||||
frame.highlightImpl(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,14 +43,8 @@ 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) {
|
||||
String attributeName = encodeTestIdAttributeName(playwright.selectors.testIdAttributeName);
|
||||
return "internal:testid=[" + attributeName + "=" + escapeForAttributeSelector(testId, true) + "]";
|
||||
return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
|
||||
}
|
||||
|
||||
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
|
||||
@@ -92,10 +86,6 @@ public class LocatorUtils {
|
||||
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
|
||||
addAttr(result, "name", name);
|
||||
}
|
||||
if (options.description != null) {
|
||||
String description = escapeForAttributeSelector(options.description, options.exact != null && options.exact);
|
||||
addAttr(result, "description", description);
|
||||
}
|
||||
if (options.pressed != null)
|
||||
addAttr(result, "pressed", options.pressed.toString());
|
||||
}
|
||||
|
||||
@@ -73,16 +73,6 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
|
||||
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snapshotOptions) {
|
||||
if (snapshotOptions == null) {
|
||||
snapshotOptions = new MatchesAriaSnapshotOptions();
|
||||
}
|
||||
FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
|
||||
options.expectedValue = Serialization.serializeArgument(expected);
|
||||
expectImpl("to.match.aria", options, expected, "Page expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAssertions not() {
|
||||
return new PageAssertionsImpl(actualPage, !isNot);
|
||||
|
||||
@@ -41,14 +41,12 @@ import static java.util.Arrays.asList;
|
||||
|
||||
|
||||
public class PageImpl extends ChannelOwner implements Page {
|
||||
final BrowserContextImpl browserContext;
|
||||
private final BrowserContextImpl browserContext;
|
||||
private final FrameImpl mainFrame;
|
||||
private final KeyboardImpl keyboard;
|
||||
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();
|
||||
@@ -139,8 +137,6 @@ 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();
|
||||
@@ -175,7 +171,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
|
||||
DownloadImpl download = new DownloadImpl(this, artifact, params);
|
||||
listeners.notify(EventType.DOWNLOAD, download);
|
||||
browserContext.notifyDownload(download);
|
||||
} else if ("fileChooser".equals(event)) {
|
||||
String guid = params.getAsJsonObject("element").get("guid").getAsString();
|
||||
ElementHandleImpl elementHandle = connection.getExistingObject(guid);
|
||||
@@ -206,7 +201,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
frame.parentFrame.childFrames.add(frame);
|
||||
}
|
||||
listeners.notify(EventType.FRAMEATTACHED, frame);
|
||||
browserContext.notifyFrameAttached(frame);
|
||||
} else if ("frameDetached".equals(event)) {
|
||||
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
|
||||
FrameImpl frame = connection.getExistingObject(guid);
|
||||
@@ -216,7 +210,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
frame.parentFrame.childFrames.remove(frame);
|
||||
}
|
||||
listeners.notify(EventType.FRAMEDETACHED, frame);
|
||||
browserContext.notifyFrameDetached(frame);
|
||||
} else if ("locatorHandlerTriggered".equals(event)) {
|
||||
int uid = params.get("uid").getAsInt();
|
||||
onLocatorHandlerTriggered(uid);
|
||||
@@ -252,7 +245,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
isClosed = true;
|
||||
browserContext.pages.remove(this);
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
browserContext.notifyPageClose(this);
|
||||
}
|
||||
|
||||
private String effectiveCloseReason() {
|
||||
@@ -559,13 +551,8 @@ 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 = new JsonObject();
|
||||
if (options.reason != null) {
|
||||
params.addProperty("reason", options.reason);
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
sendMessage("close", params, NO_TIMEOUT);
|
||||
}
|
||||
} catch (PlaywrightException exception) {
|
||||
@@ -766,11 +753,11 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoCloseable exposeBinding(String name, BindingCallback playwrightBinding) {
|
||||
return exposeBindingImpl(name, playwrightBinding);
|
||||
public AutoCloseable exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
return exposeBindingImpl(name, playwrightBinding, options);
|
||||
}
|
||||
|
||||
private AutoCloseable exposeBindingImpl(String name, BindingCallback playwrightBinding) {
|
||||
private AutoCloseable exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
|
||||
if (bindings.containsKey(name)) {
|
||||
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
|
||||
}
|
||||
@@ -781,13 +768,16 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("name", name);
|
||||
if (options != null && options.handle != null && options.handle) {
|
||||
params.addProperty("needsHandle", true);
|
||||
}
|
||||
JsonObject result = sendMessage("exposeBinding", params, NO_TIMEOUT).getAsJsonObject();
|
||||
return connection.getExistingObject(result.getAsJsonObject("disposable").get("guid").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoCloseable exposeFunction(String name, FunctionCallback playwrightFunction) {
|
||||
return exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args));
|
||||
return exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1070,11 +1060,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
return mainFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideHighlight() {
|
||||
sendMessage("hideHighlight", new JsonObject(), NO_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mouse mouse() {
|
||||
return mouse;
|
||||
@@ -1371,16 +1356,6 @@ 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));
|
||||
@@ -1482,7 +1457,6 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
void frameNavigated(FrameImpl frame) {
|
||||
listeners.notify(EventType.FRAMENAVIGATED, frame);
|
||||
browserContext.notifyFrameNavigated(frame);
|
||||
}
|
||||
|
||||
private class WaitableFrameDetach extends WaitableEvent<EventType, Frame> {
|
||||
|
||||
@@ -112,20 +112,10 @@ class FrameExpectOptions {
|
||||
}
|
||||
|
||||
class FrameExpectResult {
|
||||
static class Received {
|
||||
SerializedValue value;
|
||||
String ariaSnapshot;
|
||||
}
|
||||
boolean matches;
|
||||
Received received;
|
||||
SerializedValue received;
|
||||
String errorMessage;
|
||||
List<String> log;
|
||||
}
|
||||
|
||||
class FrameExpectErrorDetails {
|
||||
FrameExpectResult.Received received;
|
||||
Boolean timedOut;
|
||||
String customErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.ScreencastFrame;
|
||||
|
||||
class ScreencastFrameImpl implements ScreencastFrame {
|
||||
private final byte[] data;
|
||||
private final double timestamp;
|
||||
private final int viewportWidth;
|
||||
private final int viewportHeight;
|
||||
|
||||
ScreencastFrameImpl(byte[] data, double timestamp, int viewportWidth, int viewportHeight) {
|
||||
this.data = data;
|
||||
this.timestamp = timestamp;
|
||||
this.viewportWidth = viewportWidth;
|
||||
this.viewportHeight = viewportHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] data() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int viewportWidth() {
|
||||
return viewportWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int viewportHeight() {
|
||||
return viewportHeight;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package com.microsoft.playwright.impl;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Screencast;
|
||||
import com.microsoft.playwright.ScreencastFrame;
|
||||
import com.microsoft.playwright.options.ScreencastFrame;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
@@ -44,10 +44,7 @@ class ScreencastImpl implements Screencast {
|
||||
}
|
||||
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));
|
||||
onFrame.accept(new ScreencastFrame(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -77,7 +77,6 @@ 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();
|
||||
@@ -572,15 +571,6 @@ 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) {
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.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;
|
||||
}
|
||||
}
|
||||
@@ -18,21 +18,14 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Tracing;
|
||||
import com.microsoft.playwright.options.HarContentPolicy;
|
||||
import com.microsoft.playwright.options.HarMode;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TracingImpl extends ChannelOwner implements Tracing {
|
||||
@@ -41,17 +34,6 @@ class TracingImpl extends ChannelOwner implements Tracing {
|
||||
private boolean isTracing;
|
||||
private String stacksId;
|
||||
private final Set<String> additionalSources = new HashSet<>();
|
||||
final Map<String, HarRecorder> harRecorders = new HashMap<>();
|
||||
|
||||
static class HarRecorder {
|
||||
final Path path;
|
||||
final HarContentPolicy contentPolicy;
|
||||
|
||||
HarRecorder(Path har, HarContentPolicy policy) {
|
||||
this.path = har;
|
||||
this.contentPolicy = policy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -179,110 +161,6 @@ class TracingImpl extends ChannelOwner implements Tracing {
|
||||
stopChunkImpl(options == null ? null : options.path);
|
||||
}
|
||||
|
||||
private String currentHarId;
|
||||
|
||||
@Override
|
||||
public AutoCloseable startHar(Path path, StartHarOptions options) {
|
||||
if (currentHarId != null) {
|
||||
throw new PlaywrightException("HAR recording has already been started");
|
||||
}
|
||||
if (options == null) {
|
||||
options = new StartHarOptions();
|
||||
}
|
||||
boolean isZip = path.toString().endsWith(".zip");
|
||||
HarContentPolicy contentPolicy = options.content != null
|
||||
? options.content
|
||||
: (isZip ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED);
|
||||
HarMode mode = options.mode != null ? options.mode : HarMode.FULL;
|
||||
currentHarId = recordIntoHar(null, path, options.urlFilter, contentPolicy, mode, null);
|
||||
return new DisposableStub(this::stopHar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopHar() {
|
||||
if (currentHarId == null) {
|
||||
throw new PlaywrightException("HAR recording has not been started");
|
||||
}
|
||||
String harId = currentHarId;
|
||||
currentHarId = null;
|
||||
exportHar(harId);
|
||||
}
|
||||
|
||||
String recordIntoHar(PageImpl page, Path har, Object urlFilter, HarContentPolicy contentPolicy, HarMode mode, Path resourcesDir) {
|
||||
if (contentPolicy == null) {
|
||||
contentPolicy = HarContentPolicy.ATTACH;
|
||||
}
|
||||
if (mode == null) {
|
||||
mode = HarMode.MINIMAL;
|
||||
}
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
if (page != null) {
|
||||
params.add("page", page.toProtocolRef());
|
||||
}
|
||||
JsonObject recordHarArgs = new JsonObject();
|
||||
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
|
||||
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
|
||||
recordHarArgs.addProperty("mode", mode.name().toLowerCase());
|
||||
addHarUrlFilter(recordHarArgs, urlFilter);
|
||||
if (resourcesDir != null) {
|
||||
recordHarArgs.addProperty("resourcesDir", resourcesDir.toString());
|
||||
}
|
||||
if (!har.toString().endsWith(".zip")) {
|
||||
recordHarArgs.addProperty("harPath", har.toString());
|
||||
}
|
||||
|
||||
params.add("options", recordHarArgs);
|
||||
JsonObject json = sendMessage("harStart", params, NO_TIMEOUT).getAsJsonObject();
|
||||
String harId = json.get("harId").getAsString();
|
||||
harRecorders.put(harId, new HarRecorder(har, contentPolicy));
|
||||
return harId;
|
||||
}
|
||||
|
||||
void exportHar(String harId) {
|
||||
HarRecorder harParams = harRecorders.remove(harId);
|
||||
if (harParams == null) {
|
||||
return;
|
||||
}
|
||||
boolean isLocal = !connection.isRemote;
|
||||
boolean isZip = harParams.path.toString().endsWith(".zip");
|
||||
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("harId", harId);
|
||||
if (isLocal) {
|
||||
params.addProperty("mode", "entries");
|
||||
JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
|
||||
if (!isZip) {
|
||||
return;
|
||||
}
|
||||
JsonArray entries = json.getAsJsonArray("entries");
|
||||
connection.localUtils.zip(harParams.path, entries, null, false, false, java.util.Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
params.addProperty("mode", "archive");
|
||||
JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
|
||||
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
if (isZip) {
|
||||
artifact.saveAs(harParams.path);
|
||||
artifact.delete();
|
||||
return;
|
||||
}
|
||||
String tmpPath = harParams.path + ".tmp";
|
||||
artifact.saveAs(Paths.get(tmpPath));
|
||||
JsonObject unzipParams = new JsonObject();
|
||||
unzipParams.addProperty("zipFile", tmpPath);
|
||||
unzipParams.addProperty("harFile", harParams.path.toString());
|
||||
connection.localUtils.sendMessage("harUnzip", unzipParams, NO_TIMEOUT);
|
||||
artifact.delete();
|
||||
}
|
||||
|
||||
void exportAllHars() {
|
||||
for (String harId : new ArrayList<>(harRecorders.keySet())) {
|
||||
exportHar(harId);
|
||||
}
|
||||
}
|
||||
|
||||
void setTracesDir(Path tracesDir) {
|
||||
this.tracesDir = tracesDir;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
|
||||
{
|
||||
JsonObject info = new JsonObject();
|
||||
info.addProperty("phase", "before");
|
||||
info.addProperty("event", "");
|
||||
sendWaitInfo(info);
|
||||
sendWaitForEventInfo(info);
|
||||
}
|
||||
JsonObject info = new JsonObject();
|
||||
info.addProperty("phase", "after");
|
||||
@@ -52,7 +51,7 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
|
||||
info.addProperty("error", e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
sendWaitInfo(info);
|
||||
sendWaitForEventInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +61,14 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
|
||||
JsonObject info = new JsonObject();
|
||||
info.addProperty("phase", "log");
|
||||
info.addProperty("message", message);
|
||||
sendWaitInfo(info);
|
||||
sendWaitForEventInfo(info);
|
||||
}
|
||||
|
||||
private void sendWaitInfo(JsonObject info) {
|
||||
private void sendWaitForEventInfo(JsonObject info) {
|
||||
info.addProperty("event", "");
|
||||
info.addProperty("waitId", waitId);
|
||||
try {
|
||||
channel.sendMessageNoReply("__waitInfo__", info);
|
||||
} catch (RuntimeException e) {
|
||||
// Fire-and-forget: never throw to the caller.
|
||||
}
|
||||
JsonObject params = new JsonObject();
|
||||
params.add("info", info);
|
||||
channel.sendMessageAsync("waitForEventInfo", params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -17,17 +17,14 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.microsoft.playwright.WebError;
|
||||
import com.microsoft.playwright.options.WebErrorLocation;
|
||||
|
||||
public class WebErrorImpl implements WebError {
|
||||
private final PageImpl page;
|
||||
private final String error;
|
||||
private final WebErrorLocation location;
|
||||
|
||||
WebErrorImpl(PageImpl page, String error, WebErrorLocation location) {
|
||||
WebErrorImpl(PageImpl page, String error) {
|
||||
this.page = page;
|
||||
this.error = error;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,9 +36,4 @@ public class WebErrorImpl implements WebError {
|
||||
public String error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebErrorLocation location() {
|
||||
return location;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.WebSocketFrame;
|
||||
import com.microsoft.playwright.WebSocketRoute;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -68,11 +65,6 @@ class WebSocketRouteImpl extends ChannelOwner implements WebSocketRoute {
|
||||
public String url() {
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> protocols() {
|
||||
return readProtocols();
|
||||
}
|
||||
};
|
||||
|
||||
WebSocketRouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -131,22 +123,6 @@ class WebSocketRouteImpl extends ChannelOwner implements WebSocketRoute {
|
||||
return initializer.get("url").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> protocols() {
|
||||
return readProtocols();
|
||||
}
|
||||
|
||||
private List<String> readProtocols() {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (!initializer.has("protocols")) {
|
||||
return result;
|
||||
}
|
||||
for (JsonElement element : initializer.getAsJsonArray("protocols")) {
|
||||
result.add(element.getAsString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void afterHandle() {
|
||||
if (this.connected) {
|
||||
return;
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.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);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum PseudoElement {
|
||||
BEFORE,
|
||||
AFTER
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public enum ScreencastCursor {
|
||||
NONE,
|
||||
POINTER
|
||||
}
|
||||
+11
-12
@@ -16,18 +16,17 @@
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
import java.util.Map;
|
||||
/**
|
||||
* A single screencast frame delivered to {@link com.microsoft.playwright.Screencast#start Screencast.start()}'s
|
||||
* {@code onFrame} callback.
|
||||
*/
|
||||
public class ScreencastFrame {
|
||||
/**
|
||||
* JPEG-encoded frame data.
|
||||
*/
|
||||
public byte[] data;
|
||||
|
||||
public class DropPayload {
|
||||
public Object files;
|
||||
public Map<String, String> data;
|
||||
|
||||
public DropPayload setFiles(Object files) {
|
||||
this.files = files;
|
||||
return this;
|
||||
}
|
||||
public DropPayload setData(Map<String, String> data) {
|
||||
public ScreencastFrame(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@ package com.microsoft.playwright.options;
|
||||
|
||||
public class Size {
|
||||
/**
|
||||
* Max frame width in pixels.
|
||||
* Video frame width.
|
||||
*/
|
||||
public int width;
|
||||
/**
|
||||
* Max frame height in pixels.
|
||||
* Video frame height.
|
||||
*/
|
||||
public int height;
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class 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;
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class WebErrorLocation {
|
||||
/**
|
||||
* URL of the resource.
|
||||
*/
|
||||
public String url;
|
||||
/**
|
||||
* 0-based line number in the resource.
|
||||
*/
|
||||
public int line;
|
||||
/**
|
||||
* 0-based column number in the resource.
|
||||
*/
|
||||
public int column;
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.options;
|
||||
|
||||
public class WebStorageItem {
|
||||
public String name;
|
||||
public String value;
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.junit.FixtureTest;
|
||||
import com.microsoft.playwright.junit.UsePlaywright;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
@@ -31,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@FixtureTest
|
||||
@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class)
|
||||
@Tag("smoke")
|
||||
public class TestBrowser1 {
|
||||
|
||||
@Test
|
||||
@@ -115,13 +113,4 @@ public class TestBrowser1 {
|
||||
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFireContextEvent(Browser browser) {
|
||||
BrowserContext[] contextEvent = { null };
|
||||
browser.onContext(c -> contextEvent[0] = c);
|
||||
BrowserContext context = browser.newContext();
|
||||
assertEquals(context, contextEvent[0]);
|
||||
context.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.junit.FixtureTest;
|
||||
import com.microsoft.playwright.junit.UsePlaywright;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.OutputStreamWriter;
|
||||
@@ -33,7 +32,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@FixtureTest
|
||||
@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class)
|
||||
@Tag("smoke")
|
||||
public class TestBrowserContextBasic {
|
||||
@Test
|
||||
void shouldCreateNewContext(Browser browser) {
|
||||
|
||||
@@ -131,9 +131,8 @@ public class TestBrowserContextCDPSession extends TestBase {
|
||||
CDPSession session = page.context().newCDPSession(page);
|
||||
page.close();
|
||||
|
||||
// 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);
|
||||
PlaywrightException exception = assertThrows(PlaywrightException.class, session::detach);
|
||||
assertTrue(exception.getMessage().contains("Target page, context or browser has been closed"), exception.getMessage());
|
||||
context.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -186,90 +186,4 @@ public class TestBrowserContextEvents extends TestBase {
|
||||
assertTrue(webError[0].error().contains("boom"), webError[0].error());
|
||||
}
|
||||
|
||||
@Test
|
||||
void weberrorEventShouldIncludeLocation() {
|
||||
server.setRoute("/error.js", exchange -> {
|
||||
exchange.getResponseHeaders().add("content-type", "application/javascript");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("\nfunction foo() {\n throw new Error('boom');\n}\nfoo();\n");
|
||||
}
|
||||
});
|
||||
server.setRoute("/error.html", exchange -> {
|
||||
exchange.getResponseHeaders().add("content-type", "text/html");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("<script src=\"/error.js\"></script>");
|
||||
}
|
||||
});
|
||||
WebError[] webError = { null };
|
||||
context.onWebError(e -> webError[0] = e);
|
||||
page.navigate(server.PREFIX + "/error.html");
|
||||
waitForCondition(() -> webError[0] != null);
|
||||
com.microsoft.playwright.options.WebErrorLocation location = webError[0].location();
|
||||
assertEquals(server.PREFIX + "/error.js", location.url);
|
||||
assertEquals(2, location.line);
|
||||
assertTrue(location.column > 0, "expected column > 0, got " + location.column);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageLoadEventShouldWork() {
|
||||
Page[] loaded = { null };
|
||||
context.onPageLoad(p -> loaded[0] = p);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
waitForCondition(() -> loaded[0] != null);
|
||||
assertEquals(page, loaded[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void frameNavigatedEventShouldWork() {
|
||||
Frame[] navigated = { null };
|
||||
context.onFrameNavigated(f -> navigated[0] = f);
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
waitForCondition(() -> navigated[0] != null);
|
||||
assertEquals(page.mainFrame(), navigated[0]);
|
||||
assertEquals(server.EMPTY_PAGE, navigated[0].url());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageCloseEventShouldWork() {
|
||||
Page newPage = context.newPage();
|
||||
Page[] closed = { null };
|
||||
context.onPageClose(p -> closed[0] = p);
|
||||
newPage.close();
|
||||
waitForCondition(() -> closed[0] != null);
|
||||
assertEquals(newPage, closed[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void frameAttachedEventShouldWork() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
Frame[] attached = { null };
|
||||
context.onFrameAttached(f -> attached[0] = f);
|
||||
page.evaluate("() => {\n" +
|
||||
" const iframe = document.createElement('iframe');\n" +
|
||||
" iframe.src = 'about:blank';\n" +
|
||||
" document.body.appendChild(iframe);\n" +
|
||||
"}");
|
||||
waitForCondition(() -> attached[0] != null);
|
||||
assertEquals(page.mainFrame(), attached[0].parentFrame());
|
||||
}
|
||||
|
||||
@Test
|
||||
void frameDetachedEventShouldWork() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.evaluate("() => {\n" +
|
||||
" const iframe = document.createElement('iframe');\n" +
|
||||
" iframe.id = 'x';\n" +
|
||||
" iframe.src = 'about:blank';\n" +
|
||||
" document.body.appendChild(iframe);\n" +
|
||||
"}");
|
||||
page.waitForSelector("iframe");
|
||||
Frame[] detached = { null };
|
||||
context.onFrameDetached(f -> detached[0] = f);
|
||||
page.evaluate("() => document.getElementById('x').remove()");
|
||||
waitForCondition(() -> detached[0] != null);
|
||||
assertEquals(page.mainFrame(), detached[0].parentFrame());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -84,4 +84,19 @@ public class TestBrowserContextExposeFunction extends TestBase {
|
||||
assertEquals(asList("context", "page"), actualArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposeBindingHandleShouldWork() {
|
||||
JSHandle[] target = { null };
|
||||
context.exposeBinding("logme", (source, args) -> {
|
||||
target[0] = (JSHandle) args[0];
|
||||
return 17;
|
||||
}, new BrowserContext.ExposeBindingOptions().setHandle(true));
|
||||
Page page = context.newPage();
|
||||
Object result = page.evaluate("async function() {\n" +
|
||||
" return window['logme']({ foo: 42 });\n" +
|
||||
"}");
|
||||
assertNotNull(target[0]);
|
||||
assertEquals(42, target[0].evaluate("x => x.foo"));
|
||||
assertEquals(17, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -534,23 +533,6 @@ 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");
|
||||
@@ -759,12 +741,10 @@ 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("Request context disposed") ||
|
||||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
|
||||
assertTrue(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("Request context disposed") ||
|
||||
e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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,14 +40,10 @@ public class TestBrowserTypeBasic extends TestBase {
|
||||
assertEquals(getBrowserNameFromEnv(), browserType.name());
|
||||
}
|
||||
|
||||
static boolean isChromiumOrWebKit() {
|
||||
return isChromium() || isWebKit();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="isChromiumOrWebKit", disabledReason="Connecting over CDP is supported in Chromium and WebKit")
|
||||
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Non-chromium behavior")
|
||||
void shouldThrowWhenTryingToConnectWithNotChromium() {
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> browserType.connectOverCDP("foo"));
|
||||
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium and WebKit."));
|
||||
assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,11 +259,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
}
|
||||
assertFalse(browser.isConnected());
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForNavigation(() -> {}));
|
||||
// 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());
|
||||
assertTrue(e.getMessage().contains("Browser closed") || e.getMessage().contains("Page closed") || e.getMessage().contains("Browser has been closed"), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -224,9 +223,7 @@ public class TestClientCertificates extends TestBase {
|
||||
|
||||
@Test
|
||||
@DisabledIf(value="com.microsoft.playwright.TestClientCertificates#isWebKitMacOS", disabledReason="The network connection was lost.")
|
||||
// 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) {
|
||||
public void shouldWorkWithBrowserLaunchPersistentContext(@TempDir Path tmpDir) {
|
||||
BrowserType.LaunchPersistentContextOptions options = new BrowserType.LaunchPersistentContextOptions()
|
||||
.setIgnoreHTTPSErrors(true) // TODO: remove once we can pass a custom CA.
|
||||
.setClientCertificates(asList(
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
@@ -47,9 +46,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
|
||||
|
||||
|
||||
private BrowserContext persistentContext;
|
||||
// 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;
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@AfterEach
|
||||
void closePersistentContext() {
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
|
||||
@@ -253,25 +252,6 @@ 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,14 +17,12 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.KeyboardModifier;
|
||||
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;
|
||||
|
||||
@Tag("smoke")
|
||||
public class TestLocatorClick extends TestBase {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -36,18 +36,4 @@ public class TestLocatorHighlight extends TestBase {
|
||||
BoundingBox box2 = page.locator("x-pw-highlight").boundingBox();
|
||||
assertEquals(new Gson().toJson(box2), new Gson().toJson(box1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void highlightAndHideHighlightShouldNotThrow() {
|
||||
page.setContent("<input type='text' />");
|
||||
AutoCloseable disposable = page.locator("input").highlight(new Locator.HighlightOptions().setStyle("outline: 2px dashed red"));
|
||||
try {
|
||||
disposable.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
page.locator("input").highlight();
|
||||
page.locator("input").hideHighlight();
|
||||
page.hideHighlight();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,14 +341,4 @@ public class TestPageAriaSnapshot {
|
||||
" - /placeholder: Placeholder");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageMatchesAriaSnapshot(Page page) {
|
||||
page.setContent("<h1>hello</h1>");
|
||||
assertThat(page).matchesAriaSnapshot("- heading \"hello\" [level=1]");
|
||||
AssertionFailedError e = assertThrows(AssertionFailedError.class,
|
||||
() -> assertThat(page).matchesAriaSnapshot("- heading \"world\"",
|
||||
new com.microsoft.playwright.assertions.PageAssertions.MatchesAriaSnapshotOptions().setTimeout(1000)));
|
||||
org.junit.jupiter.api.Assertions.assertTrue(e.getMessage().contains("Page expected to match Aria snapshot"), e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledIf;
|
||||
|
||||
@@ -31,7 +30,6 @@ import static com.microsoft.playwright.options.LoadState.LOAD;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Tag("smoke")
|
||||
public class TestPageBasic extends TestBase {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.DropPayload;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestPageDrop extends TestBase {
|
||||
private void setupDropzone() {
|
||||
page.setContent("<style>#dropzone { width: 300px; height: 200px; border: 2px dashed #888; }</style>\n" +
|
||||
"<div id=\"dropzone\"></div>\n" +
|
||||
"<script>\n" +
|
||||
" window.__dropInfo = null;\n" +
|
||||
" const zone = document.getElementById('dropzone');\n" +
|
||||
" zone.addEventListener('dragenter', e => e.preventDefault());\n" +
|
||||
" zone.addEventListener('dragover', e => e.preventDefault());\n" +
|
||||
" zone.addEventListener('drop', async e => {\n" +
|
||||
" e.preventDefault();\n" +
|
||||
" const files = [];\n" +
|
||||
" for (const file of e.dataTransfer.files)\n" +
|
||||
" files.push({ name: file.name, type: file.type, size: file.size, text: await file.text() });\n" +
|
||||
" const data = {};\n" +
|
||||
" for (const t of e.dataTransfer.types) {\n" +
|
||||
" if (t !== 'Files')\n" +
|
||||
" data[t] = e.dataTransfer.getData(t);\n" +
|
||||
" }\n" +
|
||||
" window.__dropInfo = { files, data };\n" +
|
||||
" });\n" +
|
||||
"</script>");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> waitForDropInfo() {
|
||||
page.waitForCondition(() -> page.evaluate("window.__dropInfo") != null);
|
||||
return (Map<String, Object>) page.evaluate("window.__dropInfo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDropFilePayload() {
|
||||
setupDropzone();
|
||||
page.locator("#dropzone").drop(new DropPayload().setFiles(new FilePayload("note.txt", "text/plain", "hello".getBytes(StandardCharsets.UTF_8))));
|
||||
Map<String, Object> info = waitForDropInfo();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> files = (List<Map<String, Object>>) info.get("files");
|
||||
assertEquals(1, files.size());
|
||||
assertEquals("note.txt", files.get(0).get("name"));
|
||||
assertEquals("text/plain", files.get(0).get("type"));
|
||||
assertEquals("hello", files.get(0).get("text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDropMultipleFilePayloads() {
|
||||
setupDropzone();
|
||||
page.locator("#dropzone").drop(new DropPayload().setFiles(new FilePayload[] {
|
||||
new FilePayload("a.txt", "text/plain", "AAA".getBytes(StandardCharsets.UTF_8)),
|
||||
new FilePayload("b.txt", "text/plain", "BB".getBytes(StandardCharsets.UTF_8)),
|
||||
}));
|
||||
Map<String, Object> info = waitForDropInfo();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> files = (List<Map<String, Object>>) info.get("files");
|
||||
assertEquals(2, files.size());
|
||||
assertEquals("a.txt", files.get(0).get("name"));
|
||||
assertEquals("AAA", files.get(0).get("text"));
|
||||
assertEquals("b.txt", files.get(1).get("name"));
|
||||
assertEquals("BB", files.get(1).get("text"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDropClipboardLikeData() {
|
||||
setupDropzone();
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("text/plain", "hello world");
|
||||
data.put("text/uri-list", "https://example.com");
|
||||
page.locator("#dropzone").drop(new DropPayload().setData(data));
|
||||
Map<String, Object> info = waitForDropInfo();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<?> files = (List<?>) info.get("files");
|
||||
assertTrue(files.isEmpty(), "expected no files");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> droppedData = (Map<String, String>) info.get("data");
|
||||
assertEquals("hello world", droppedData.get("text/plain"));
|
||||
assertEquals("https://example.com", droppedData.get("text/uri-list"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDropFileByLocalPath(@org.junit.jupiter.api.io.TempDir Path dir) throws Exception {
|
||||
setupDropzone();
|
||||
Path filePath = dir.resolve("hello.txt");
|
||||
Files.write(filePath, "path-content".getBytes(StandardCharsets.UTF_8));
|
||||
page.locator("#dropzone").drop(new DropPayload().setFiles(filePath));
|
||||
Map<String, Object> info = waitForDropInfo();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> files = (List<Map<String, Object>>) info.get("files");
|
||||
assertEquals(1, files.size());
|
||||
assertEquals("hello.txt", files.get(0).get("name"));
|
||||
assertEquals("path-content", files.get(0).get("text"));
|
||||
}
|
||||
}
|
||||
@@ -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, 2000));\n" +
|
||||
" await new Promise(f => window.setTimeout(f, 100));\n" +
|
||||
" }");
|
||||
|
||||
List<String> errors = page.pageErrors();
|
||||
|
||||
@@ -164,6 +164,35 @@ public class TestPageExposeFunction extends TestBase {
|
||||
assertEquals( 7, ((Map) result).get("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposeBindingHandleShouldWork() {
|
||||
JSHandle[] target = { null };
|
||||
page.exposeBinding("logme", (source, args) -> {
|
||||
target[0] = (JSHandle) args[0];
|
||||
return 17;
|
||||
}, new Page.ExposeBindingOptions().setHandle(true));
|
||||
Object result = page.evaluate("async function() {\n" +
|
||||
" return window['logme']({ foo: 42 });\n" +
|
||||
"}");
|
||||
assertEquals(42, target[0].evaluate("x => x.foo"));
|
||||
assertEquals(17, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposeBindingHandleShouldNotThrowDuringNavigation() {
|
||||
page.exposeBinding("logme", (source, args) -> {
|
||||
return 17;
|
||||
}, new Page.ExposeBindingOptions().setHandle(true));
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
|
||||
page.waitForNavigation(new Page.WaitForNavigationOptions().setWaitUntil(LOAD), () -> {
|
||||
page.evaluate("async url => {\n" +
|
||||
" window['logme']({ foo: 42 });\n" +
|
||||
" window.location.href = url;\n" +
|
||||
"}", server.PREFIX + "/one-style.html");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowForDuplicateRegistrations() {
|
||||
page.exposeFunction("foo", args -> null);
|
||||
@@ -173,6 +202,28 @@ public class TestPageExposeFunction extends TestBase {
|
||||
assertTrue(e.getMessage().contains("Function \"foo\" has been already registered"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposeBindingHandleShouldThrowForMultipleArguments() {
|
||||
page.exposeBinding("logme", (source, args) -> {
|
||||
return 17;
|
||||
}, new Page.ExposeBindingOptions().setHandle(true));
|
||||
assertEquals(17, page.evaluate("async function() {\n" +
|
||||
" return window['logme']({ foo: 42 });\n" +
|
||||
"}"));
|
||||
assertEquals(17, page.evaluate("async function() {\n" +
|
||||
" return window['logme']({ foo: 42 }, undefined, undefined);\n" +
|
||||
"}"));
|
||||
assertEquals(17, page.evaluate("async function() {\n" +
|
||||
" return window['logme'](undefined, undefined, undefined);\n" +
|
||||
"}"));
|
||||
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
|
||||
page.evaluate("async function() {\n" +
|
||||
" return window['logme'](1, 2);\n" +
|
||||
"}");
|
||||
});
|
||||
assertTrue(e.getMessage().contains("exposeBindingHandle supports a single argument, 2 received"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSerializeCycles() {
|
||||
Object[] object = { null };
|
||||
|
||||
@@ -45,13 +45,13 @@ public class TestRouteWebSocket {
|
||||
}
|
||||
private void setupWS(Frame target, Server server, int port, String binaryType) {
|
||||
target.navigate(server.EMPTY_PAGE);
|
||||
// No 'error' listener: WebKit fires a spurious 'error' before 'close' on non-normal closures (e.g. 1008).
|
||||
target.evaluate("({ port, binaryType }) => {\n" +
|
||||
" window.log = [];\n" +
|
||||
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
|
||||
" window.ws.binaryType = binaryType;\n" +
|
||||
" window.ws.addEventListener('open', () => window.log.push('open'));\n" +
|
||||
" window.ws.addEventListener('close', event => window.log.push(`close code=${event.code} reason=${event.reason}`));\n" +
|
||||
" window.ws.addEventListener('error', event => window.log.push(`error`));\n" +
|
||||
" window.ws.addEventListener('message', async event => {\n" +
|
||||
" let data;\n" +
|
||||
" if (typeof event.data === 'string')\n" +
|
||||
@@ -391,29 +391,4 @@ public class TestRouteWebSocket {
|
||||
});
|
||||
assertEquals(asList("response"), page.evaluate("window.log"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldExposeProtocolsToTheRouteHandler(Page page, Server server) {
|
||||
List<com.microsoft.playwright.WebSocketRoute> routes = new ArrayList<>();
|
||||
page.routeWebSocket(Pattern.compile(".*"), ws -> routes.add(ws));
|
||||
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
int port = webSocketServer.getPort();
|
||||
page.evaluate("({ port }) => {\n" +
|
||||
" window.wsNone = new WebSocket('ws://localhost:' + port + '/ws-none');\n" +
|
||||
" window.wsString = new WebSocket('ws://localhost:' + port + '/ws-string', 'chat.v1');\n" +
|
||||
" window.wsArray = new WebSocket('ws://localhost:' + port + '/ws-array', ['chat.v2', 'chat.v1']);\n" +
|
||||
"}", mapOf("port", port));
|
||||
|
||||
page.waitForCondition(() -> routes.size() == 3);
|
||||
|
||||
java.util.Map<String, com.microsoft.playwright.WebSocketRoute> byUrl = new java.util.HashMap<>();
|
||||
for (com.microsoft.playwright.WebSocketRoute r : routes) {
|
||||
String path = java.net.URI.create(r.url()).getPath();
|
||||
byUrl.put(path, r);
|
||||
}
|
||||
assertEquals(asList(), byUrl.get("/ws-none").protocols());
|
||||
assertEquals(asList("chat.v1"), byUrl.get("/ws-string").protocols());
|
||||
assertEquals(asList("chat.v2", "chat.v1"), byUrl.get("/ws-array").protocols());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.ScreencastFrame;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
@@ -112,31 +113,9 @@ public class TestScreencast extends TestBase {
|
||||
assertFalse(frames.isEmpty(), "expected at least one frame");
|
||||
// JPEG-encoded frames start with FF D8.
|
||||
for (ScreencastFrame frame : frames) {
|
||||
assertNotNull(frame.data());
|
||||
assertEquals((byte) 0xFF, frame.data()[0]);
|
||||
assertEquals((byte) 0xD8, frame.data()[1]);
|
||||
}
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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).setSize(500, 400));
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.evaluate("() => document.body.style.backgroundColor = 'red'");
|
||||
page.waitForTimeout(500);
|
||||
page.screencast().stop();
|
||||
assertFalse(frames.isEmpty(), "expected at least one frame");
|
||||
for (ScreencastFrame frame : frames) {
|
||||
assertEquals(1000, frame.viewportWidth());
|
||||
assertEquals(400, frame.viewportHeight());
|
||||
assertTrue(frame.timestamp() > 0, "expected a positive timestamp, got " + frame.timestamp());
|
||||
assertNotNull(frame.data);
|
||||
assertEquals((byte) 0xFF, frame.data[0]);
|
||||
assertEquals((byte) 0xD8, frame.data[1]);
|
||||
}
|
||||
} finally {
|
||||
context.close();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -25,7 +24,6 @@ import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Tag("smoke")
|
||||
public class TestSelectorsCss extends TestBase {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -51,20 +51,6 @@ 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");
|
||||
|
||||
@@ -448,7 +448,7 @@ public class TestSelectorsRole extends TestBase {
|
||||
assertTrue(e0.getMessage().contains("Role must not be empty"), e0.getMessage());
|
||||
|
||||
PlaywrightException e1 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=foo[sElected]"));
|
||||
assertTrue(e1.getMessage().contains("Unknown attribute \"sElected\", must be one of \"checked\", \"description\", \"disabled\", \"expanded\", \"include-hidden\", \"level\", \"name\", \"pressed\", \"selected\""), e1.getMessage());
|
||||
assertTrue(e1.getMessage().contains("Unknown attribute \"sElected\", must be one of \"checked\", \"disabled\", \"expanded\", \"include-hidden\", \"level\", \"name\", \"pressed\", \"selected\""), e1.getMessage());
|
||||
|
||||
PlaywrightException e2 = assertThrows(PlaywrightException.class, () -> page.querySelector("role=foo[bar . qux=true]"));
|
||||
assertTrue(e2.getMessage().contains("Unknown attribute \"bar.qux\""), e2.getMessage());
|
||||
|
||||
@@ -158,7 +158,6 @@ public class TestTracing extends TestBase {
|
||||
Pattern.compile("Set content"),
|
||||
Pattern.compile("Click")
|
||||
});
|
||||
traceViewer.selectAction("Click");
|
||||
traceViewer.showSourceTab();
|
||||
assertThat(traceViewer.stackFrames()).containsText(new Pattern[] {
|
||||
Pattern.compile("myMethodInner"),
|
||||
@@ -380,15 +379,4 @@ public class TestTracing extends TestBase {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRecordHarWithStartHarStopHar(@TempDir Path tempDir) throws Exception {
|
||||
Path harPath = tempDir.resolve("tracing.har");
|
||||
context.tracing().startHar(harPath, new Tracing.StartHarOptions().setMode(com.microsoft.playwright.options.HarMode.MINIMAL));
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
context.tracing().stopHar();
|
||||
String content = new String(Files.readAllBytes(harPath));
|
||||
assertTrue(content.contains("\"log\""), content);
|
||||
assertTrue(content.contains("/one-style.html"), content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import 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"));
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ class TraceViewerPage {
|
||||
}
|
||||
|
||||
Locator stackFrames() {
|
||||
return this.page.getByRole(AriaRole.LISTBOX, new Page.GetByRoleOptions().setName("stack trace")).getByRole(AriaRole.OPTION);
|
||||
return this.page.getByRole(AriaRole.LIST, new Page.GetByRoleOptions().setName("stack trace")).getByRole(AriaRole.LISTITEM);
|
||||
}
|
||||
|
||||
void selectAction(String title, int ordinal) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 45 KiB |
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.61.0</version>
|
||||
<version>1.59.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Playwright Parent Project</name>
|
||||
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
|
||||
@@ -44,11 +44,11 @@
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.parameters>true</maven.compiler.parameters>
|
||||
<gson.version>2.14.0</gson.version>
|
||||
<gson.version>2.13.2</gson.version>
|
||||
<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.18</slf4j.version>
|
||||
<slf4j.version>2.0.17</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.6</version>
|
||||
<version>3.5.5</version>
|
||||
<configuration>
|
||||
<properties>
|
||||
<configurationParameters>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.61.1
|
||||
1.59.1-beta-1775762078000
|
||||
|
||||
+32
-80
@@ -8,12 +8,8 @@ cd "$(dirname $0)"
|
||||
|
||||
if [[ ($1 == '-h') || ($1 == '--help') ]]; then
|
||||
echo ""
|
||||
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 "This script for downloading playwright driver for all platforms."
|
||||
echo "The downloaded files will be put under 'driver-bundle/src/main/resources/driver'."
|
||||
echo ""
|
||||
echo "Usage: scripts/download_driver.sh [option]"
|
||||
echo ""
|
||||
@@ -23,89 +19,45 @@ 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
|
||||
|
||||
# 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
|
||||
cd ../driver-bundle/src/main/resources
|
||||
|
||||
if [[ -d 'driver' ]]; then
|
||||
echo "Deleting existing drivers from $(pwd)"
|
||||
rm -rf driver
|
||||
fi
|
||||
|
||||
# The Node.js version is kept in sync with the driver version in the upstream build script.
|
||||
NODE_VERSION=$(curl -fsSL "https://raw.githubusercontent.com/microsoft/playwright/$GIT_HEAD/utils/build/build-playwright-driver.sh" \
|
||||
| sed -n 's/^NODE_VERSION="\([^"]*\)".*/\1/p')
|
||||
if [[ -z "$NODE_VERSION" ]]; then
|
||||
echo "Failed to determine Node.js version for playwright@$DRIVER_VERSION ($GIT_HEAD)"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p driver
|
||||
cd driver
|
||||
|
||||
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"
|
||||
for PLATFORM in mac mac-arm64 linux linux-arm64 win32_x64
|
||||
do
|
||||
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"
|
||||
FILE_NAME=$FILE_PREFIX-$PLATFORM.zip
|
||||
mkdir $PLATFORM
|
||||
cd $PLATFORM
|
||||
echo "Downloading driver for $PLATFORM to $(pwd)"
|
||||
|
||||
# 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
|
||||
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"
|
||||
URL=https://cdn.playwright.dev/builds/driver
|
||||
if [[ "$DRIVER_VERSION" == *-alpha* || "$DRIVER_VERSION" == *-beta* || "$DRIVER_VERSION" == *-next* ]]; then
|
||||
URL=$URL/next
|
||||
fi
|
||||
rm -f "$NODE_ARCHIVE"
|
||||
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
|
||||
else
|
||||
curl -O $URL
|
||||
fi
|
||||
unzip $FILE_NAME -d .
|
||||
rm $FILE_NAME
|
||||
|
||||
cd -
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "All drivers have been successfully assembled."
|
||||
echo "All drivers have been successfully downloaded."
|
||||
echo ""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user