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

Compare commits

..

1 Commits

Author SHA1 Message Date
Yury Semikhatsky c433c1d6e5 chore: mark 1.59.0 (#1909) 2026-04-09 14:14:15 -07:00
69 changed files with 374 additions and 1709 deletions
@@ -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.
+5 -39
View File
@@ -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)
@@ -199,4 +164,5 @@ 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
-8
View File
@@ -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
+6 -31
View File
@@ -28,49 +28,24 @@ jobs:
matrix:
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: |
+2 -2
View File
@@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->148.0.7778.96<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| 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 -->150.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
## Documentation
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
</parent>
<artifactId>driver</artifactId>
+2 -2
View File
@@ -6,11 +6,11 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.60.0</playwright.version>
<playwright.version>1.59.0</playwright.version>
</properties>
<dependencies>
<dependency>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</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();
}
@@ -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".
@@ -785,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
@@ -133,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.
@@ -168,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.
@@ -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;
@@ -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.
*/
@@ -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>
@@ -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;
/**
@@ -40,7 +40,7 @@ public interface Screencast {
public Integer quality;
/**
* 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;
@@ -1,34 +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();
/**
* 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();
}
@@ -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.
*
@@ -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"));
@@ -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;
@@ -68,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,
@@ -134,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);
@@ -188,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);
@@ -363,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);
}
@@ -451,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");
}
@@ -468,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
@@ -571,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
@@ -857,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() {
@@ -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);
}
@@ -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
@@ -86,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,7 +41,7 @@ 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;
@@ -171,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);
@@ -202,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);
@@ -212,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);
@@ -248,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() {
@@ -757,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");
}
@@ -772,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
@@ -1061,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;
@@ -1463,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,12 +112,8 @@ class FrameExpectOptions {
}
class FrameExpectResult {
static class Received {
SerializedValue value;
String ariaSnapshot;
}
boolean matches;
Received received;
SerializedValue received;
String errorMessage;
List<String> log;
}
@@ -1,46 +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 int viewportWidth;
private final int viewportHeight;
ScreencastFrameImpl(byte[] data, int viewportWidth, int viewportHeight) {
this.data = data;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
}
@Override
public byte[] data() {
return data;
}
@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,9 +44,7 @@ class ScreencastImpl implements Screencast {
}
String dataBase64 = params.get("data").getAsString();
byte[] data = java.util.Base64.getDecoder().decode(dataBase64);
int viewportWidth = params.get("viewportWidth").getAsInt();
int viewportHeight = params.get("viewportHeight").getAsInt();
onFrame.accept(new ScreencastFrameImpl(data, viewportWidth, viewportHeight));
onFrame.accept(new ScreencastFrame(data));
}
@Override
@@ -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;
}
@@ -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,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
}
@@ -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;
}
}
}
@@ -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;
}
@@ -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) {
@@ -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());
}
}
@@ -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);
}
}
@@ -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"));
}
}
@@ -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,30 +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 onFrameShouldReceiveViewportSize() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setViewportSize(1000, 400));
Page page = context.newPage();
try {
List<ScreencastFrame> frames = new ArrayList<>();
page.screencast().start(new Screencast.StartOptions().setOnFrame(frames::add));
page.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());
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
@@ -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);
}
}
@@ -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) {
+2 -2
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.50.0-SNAPSHOT</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,7 +44,7 @@
<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>
+1 -1
View File
@@ -1 +1 @@
1.60.0
1.59.1-beta-1775762078000
+1 -1
View File
@@ -50,7 +50,7 @@ do
if command -v wget &> /dev/null; then
wget $URL
else
curl --retry 5 --retry-delay 2 -fL -O $URL
curl -O $URL
fi
unzip $FILE_NAME -d .
rm $FILE_NAME
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -326,22 +326,6 @@ class TypeRef extends Element {
}
return;
}
if ("function".equals(jsonObject.get("name").getAsString()) && jsonObject.has("args")) {
for (JsonElement item : jsonObject.getAsJsonArray("args")) {
if (!item.isJsonObject()) {
continue;
}
JsonObject argObject = item.getAsJsonObject();
if (!"Object".equals(argObject.get("name").getAsString())) {
continue;
}
String alias = javaAlias(argObject);
if (alias != null) {
typeScope().createTopLevelInterface(alias, this, argObject);
}
}
return;
}
if ("Object".equals(jsonObject.get("name").getAsString())) {
if (customType != null) {
// Same type maybe referenced as 'Object' in several union values, e.g. Object|Array<Object>
@@ -522,8 +506,8 @@ class TypeRef extends Element {
if (customType != null) {
return customType;
}
// Inner Objects without langAliases (e.g. unaliased function arguments) are not visited
// by createClassesAndEnums, so resolve their Java type name from langAliases here.
// Inner Objects (e.g. function arguments) are not visited by createClassesAndEnums,
// so resolve their Java type name from langAliases here.
String alias = javaAlias(jsonType);
if (alias != null) {
return alias;
@@ -617,14 +601,6 @@ abstract class TypeDefinition extends Element {
}
}
void createTopLevelInterface(String name, Element parent, JsonObject jsonObject) {
Map<String, TypeDefinition> map = topLevelTypes();
TypeDefinition existing = map.putIfAbsent(name, new CustomInterface(parent, name, jsonObject));
if (existing != null && !(existing instanceof CustomInterface)) {
throw new RuntimeException("Two interfaces with same name have different values:\n" + jsonObject + "\n" + existing.jsonElement);
}
}
void createNestedClass(String name, Element parent, JsonObject jsonObject) {
for (CustomClass c : classes) {
if (c.name.equals(name)) {
@@ -864,7 +840,7 @@ class Field extends Element {
final String name;
final TypeRef type;
Field(TypeDefinition parent, String name, JsonObject jsonElement) {
Field(CustomClass parent, String name, JsonObject jsonElement) {
super(parent, jsonElement);
this.name = name;
this.type = new TypeRef(this, jsonElement.getAsJsonObject().get("type"));
@@ -1000,7 +976,7 @@ class Interface extends TypeDefinition {
if (methods.stream().anyMatch(m -> "create".equals(m.jsonName))) {
output.add("import com.microsoft.playwright.impl." + jsonName + "Impl;");
}
if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "FrameLocator", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard", "Tracing", "Video", "Debugger", "Screencast", "WebError").contains(jsonName)) {
if (asList("Page", "Request", "Response", "APIRequestContext", "APIRequest", "APIResponse", "FileChooser", "Frame", "FrameLocator", "ElementHandle", "Locator", "Browser", "BrowserContext", "BrowserType", "Mouse", "Keyboard", "Tracing", "Video", "Debugger", "Screencast").contains(jsonName)) {
output.add("import com.microsoft.playwright.options.*;");
}
if ("Download".equals(jsonName)) {
@@ -1012,7 +988,7 @@ class Interface extends TypeDefinition {
if ("Clock".equals(jsonName)) {
output.add("import java.util.Date;");
}
if (asList("Page", "Frame", "ElementHandle", "Locator", "LocatorAssertions", "APIRequest", "Browser", "BrowserContext", "BrowserType", "Route", "Request", "Response", "JSHandle", "ConsoleMessage", "APIResponse", "Playwright", "Debugger", "Screencast", "WebSocketRoute").contains(jsonName)) {
if (asList("Page", "Frame", "ElementHandle", "Locator", "LocatorAssertions", "APIRequest", "Browser", "BrowserContext", "BrowserType", "Route", "Request", "Response", "JSHandle", "ConsoleMessage", "APIResponse", "Playwright", "Debugger", "Screencast").contains(jsonName)) {
output.add("import java.util.*;");
}
if (asList("WebSocketRoute").contains(jsonName)) {
@@ -1028,7 +1004,7 @@ class Interface extends TypeDefinition {
if (asList("Page", "Frame", "BrowserContext", "WebSocket", "Worker").contains(jsonName)) {
output.add("import java.util.function.Predicate;");
}
if (asList("Page", "Frame", "FrameLocator", "Locator", "Browser", "BrowserType", "BrowserContext", "PageAssertions", "LocatorAssertions", "Tracing").contains(jsonName)) {
if (asList("Page", "Frame", "FrameLocator", "Locator", "Browser", "BrowserType", "BrowserContext", "PageAssertions", "LocatorAssertions").contains(jsonName)) {
output.add("import java.util.regex.Pattern;");
}
if ("CDPSession".equals(jsonName)) {
@@ -1036,7 +1012,6 @@ class Interface extends TypeDefinition {
}
if ("LocatorAssertions".equals(jsonName)) {
output.add("import com.microsoft.playwright.options.AriaRole;");
output.add("import com.microsoft.playwright.options.PseudoElement;");
}
if ("PlaywrightAssertions".equals(jsonName)) {
output.add("import com.microsoft.playwright.APIResponse;");
@@ -1134,10 +1109,6 @@ class CustomClass extends TypeDefinition {
output.add("import java.nio.file.Path;");
output.add("");
}
if (asList("DropPayload").contains(name)) {
output.add("import java.util.Map;");
output.add("");
}
String access = (parent.typeScope() instanceof CustomClass) || topLevelTypes().containsKey(name) ? "public " : "";
output.add(offset + access + "class " + name + " {");
String bodyOffset = offset + " ";
@@ -1175,43 +1146,6 @@ class CustomClass extends TypeDefinition {
}
}
class CustomInterface extends TypeDefinition {
final String name;
final List<Field> fields = new ArrayList<>();
CustomInterface(Element parent, String name, JsonObject jsonElement) {
super(parent, true, jsonElement);
this.name = name;
if (jsonElement.has("properties")) {
for (JsonElement item : jsonElement.getAsJsonArray("properties")) {
JsonObject propertyJson = item.getAsJsonObject();
fields.add(new Field(this, propertyJson.get("name").getAsString(), propertyJson));
}
}
}
@Override
String name() {
return name;
}
@Override
void writeTo(List<String> output, String offset) {
output.add(offset + "public interface " + name + " {");
String bodyOffset = offset + " ";
boolean first = true;
for (Field f : fields) {
if (!first) {
output.add("");
}
first = false;
writeJavadoc(output, bodyOffset, f.comment());
output.add(bodyOffset + f.type.toJava() + " " + f.name + "();");
}
output.add(offset + "}");
}
}
class Enum extends TypeDefinition {
final List<String> enumValues;
@@ -1251,37 +1185,12 @@ public class ApiGenerator {
filterOtherLangs(api, new Stack<>());
File dir = new File(cwd, "playwright/src/main/java/com/microsoft/playwright");
File optionsDir = new File(dir, "options");
System.out.println("Writing files to: " + dir.getCanonicalPath());
Map<String, TypeDefinition> sharedTypes = new HashMap<>();
generate(api, dir, "com.microsoft.playwright", isAssertion().negate(), sharedTypes);
generate(api, dir, "com.microsoft.playwright", isAssertion().negate());
File assertionsDir = new File(cwd,"playwright/src/main/java/com/microsoft/playwright/assertions");
System.out.println("Writing assertion files to: " + dir.getCanonicalPath());
generate(api, assertionsDir, "com.microsoft.playwright.assertions", isAssertion().and(isSoftAssertion().negate()), sharedTypes);
writeTopLevelTypes(sharedTypes, dir, optionsDir, "com.microsoft.playwright");
}
private void writeTopLevelTypes(Map<String, TypeDefinition> topLevelTypes, File dir, File optionsDir, String packageName) throws IOException {
for (TypeDefinition e : topLevelTypes.values()) {
List<String> lines = new ArrayList<>();
lines.add(Interface.header);
File targetDir;
if (e instanceof CustomInterface) {
lines.add("package " + packageName + ";");
targetDir = dir;
} else {
lines.add("package " + packageName + ".options;");
targetDir = optionsDir;
}
lines.add("");
e.writeTo(lines, "");
String text = String.join("\n", lines);
try (FileWriter writer = new FileWriter(new File(targetDir, e.name() + ".java"))) {
writer.write(text);
}
}
generate(api, assertionsDir, "com.microsoft.playwright.assertions", isAssertion().and(isSoftAssertion().negate()));
}
private static Predicate<String> isAssertion() {
@@ -1297,7 +1206,8 @@ public class ApiGenerator {
return className -> className.contains("SoftAssertions");
}
private void generate(JsonArray api, File dir, String packageName, Predicate<String> classFilter, Map<String, TypeDefinition> topLevelTypes) throws IOException {
private void generate(JsonArray api, File dir, String packageName, Predicate<String> classFilter) throws IOException {
Map<String, TypeDefinition> topLevelTypes = new HashMap<>();
for (JsonElement entry: api) {
String name = entry.getAsJsonObject().get("name").getAsString();
// We write this one manually.
@@ -1323,6 +1233,23 @@ public class ApiGenerator {
}
}
// No options under assertions.
if (packageName.contains(".assertions")) {
return;
}
dir = new File(dir, "options");
for (TypeDefinition e : topLevelTypes.values()) {
List<String> lines = new ArrayList<>();
lines.add(Interface.header);
lines.add("package " + packageName + ".options;");
lines.add("");
e.writeTo(lines, "");
String text = String.join("\n", lines);
try (FileWriter writer = new FileWriter(new File(dir, e.name() + ".java"))) {
writer.write(text);
}
}
}
private static void filterOtherLangs(JsonElement json, Stack<String> path) {
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-fatjar</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Test Playwright Command Line FatJar</name>
<properties>
<compiler.version>1.8</compiler.version>
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-version</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Test Playwright Command Line Version</name>
<properties>
<compiler.version>1.8</compiler.version>
@@ -14,6 +14,6 @@ cp -R ../../driver-bundle/src/test/ $PROJECT_DIR/src/
cp -R ../../playwright/src/test/ $PROJECT_DIR/src/
cd $PROJECT_DIR
mvn test --no-transfer-progress "$@"
mvn test --no-transfer-progress
rm -rf $PROJECT_DIR
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-local-installation</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Test local installation</name>
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
<properties>
+1 -1
View File
@@ -9,7 +9,7 @@
</parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Test Playwright With Spring Boot</name>
<properties>
<spring.version>2.4.3</spring.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>update-version</artifactId>
<version>1.50.0-SNAPSHOT</version>
<version>1.59.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on