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

Compare commits

..

16 Commits

Author SHA1 Message Date
Yury Semikhatsky 66dd81777b chore: update Maven version to 1.58.0 (#1887) 2026-01-28 14:58:24 -08:00
Simon Knott b5c2160d32 chore: roll to 1.58.0 (#1883) 2026-01-28 10:39:26 -08:00
Yury Semikhatsky 63bb008857 chore: roll driver to 1.57.0-beta-1764692940000 (#1870) 2025-12-02 13:59:36 -08:00
dependabot[bot] 9b3a788806 chore(deps): bump actions/checkout from 5 to 6 in the actions group (#1868) 2025-12-01 15:00:50 -08:00
dependabot[bot] 2f387edf0d chore(deps): bump the all group with 3 updates (#1867) 2025-12-01 14:59:52 -08:00
arukiidou 6eb30e275c chore(deps): bump junit.version from 5.13.4 to 5.14.1 (#1859) 2025-11-12 10:00:13 -08:00
arukiidou 0f14588df1 Migrate ExtensionContext.Store.CloseableResource to AutoCloseable (#1860) 2025-11-12 09:59:06 -08:00
arukiidou e417cad372 Fix document typo - setContextOptions (#1862) 2025-11-12 09:45:13 -08:00
Yury Semikhatsky 059667e311 chore: remove background pages implementation (#1861) 2025-10-31 10:25:35 -07:00
Yury Semikhatsky 98296d9cdf devops: update ado approver (#1857) 2025-10-24 13:38:05 -07:00
Yury Semikhatsky 1599e1c7bc chore: roll 1.56.1 (#1855) 2025-10-17 11:11:43 -07:00
dependabot[bot] a2555ddf9e chore(deps): bump the all group with 4 updates (#1846) 2025-10-03 15:46:53 -07:00
Yury Semikhatsky 0deadc2b90 chore: roll driver to 1.56.0-beta (#1849) 2025-10-03 15:45:53 -07:00
Simon Knott eb1fea9907 fix(trace): waitForLoadState title (#1840) 2025-09-08 09:49:00 +02:00
dependabot[bot] dd99ce8b34 chore(deps): bump the actions group with 2 updates (#1838) 2025-09-04 16:18:58 -07:00
dependabot[bot] ed8e9c434f chore(deps): bump the all group across 1 directory with 7 updates (#1839) 2025-09-04 16:18:15 -07:00
63 changed files with 943 additions and 200 deletions
+1 -1
View File
@@ -90,7 +90,7 @@ extends:
folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com'
approvers: 'yurys@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
+22
View File
@@ -0,0 +1,22 @@
---
name: playwright-roll
description: Roll Playwright Java to a new version
---
Help the user roll to a new version of Playwright.
ROLLING.md contains general instructions and scripts.
Start with updating the version and generating the API to see the state of things.
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)
- adding a couple of new tests to verify new/changed functionality
## 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
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Azure login
uses: azure/login@v2
with:
@@ -26,5 +26,5 @@ jobs:
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: ./utils/docker/publish_docker.sh stable
+6 -6
View File
@@ -20,9 +20,9 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 8
@@ -65,13 +65,13 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 8
@@ -100,9 +100,9 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up JDK 21
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: adopt
java-version: 21
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Cache Maven packages
uses: actions/cache@v4
with:
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build Docker image
run: |
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Download drivers
run: scripts/download_driver.sh
- name: Regenerate APIs
+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 -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->145.0.7632.6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->146.0.1<!-- 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.55.0</version>
<version>1.58.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.55.0</version>
<version>1.58.0</version>
</parent>
<artifactId>driver</artifactId>
+2 -2
View File
@@ -6,11 +6,11 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.55.0</version>
<version>1.58.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.55.0</playwright.version>
<playwright.version>1.58.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.55.0</version>
<version>1.58.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -46,14 +46,7 @@ import java.util.regex.Pattern;
public interface BrowserContext extends AutoCloseable {
/**
* <strong>NOTE:</strong> Only works with Chromium browser's persistent context.
*
* <p> Emitted when new background page is created in the context.
* <pre>{@code
* context.onBackgroundPage(backgroundPage -> {
* System.out.println(backgroundPage.url());
* });
* }</pre>
* @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions.
*/
void onBackgroundPage(Consumer<Page> handler);
/**
@@ -588,9 +581,7 @@ public interface BrowserContext extends AutoCloseable {
*/
void addInitScript(Path script);
/**
* <strong>NOTE:</strong> Background pages are only supported on Chromium-based browsers.
*
* <p> All existing background pages in the context.
* @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions.
*
* @since v1.11
*/
@@ -865,6 +856,8 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "local-fonts"}</li>
* <li> {@code "local-network-access"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
@@ -872,7 +865,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul>
* @since v1.8
*/
@@ -898,6 +890,8 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "local-fonts"}</li>
* <li> {@code "local-network-access"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
@@ -905,7 +899,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul>
* @since v1.8
*/
@@ -128,6 +128,11 @@ public interface BrowserType {
* Additional HTTP headers to be sent with connect request. Optional.
*/
public Map<String, String> headers;
/**
* Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely upon
* the file system being the same between Playwright and the Browser.
*/
public Boolean isLocal;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@@ -146,6 +151,14 @@ public interface BrowserType {
this.headers = headers;
return this;
}
/**
* Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely upon
* the file system being the same between Playwright and the Browser.
*/
public ConnectOverCDPOptions setIsLocal(boolean isLocal) {
this.isLocal = isLocal;
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.
@@ -186,10 +199,6 @@ public interface BrowserType {
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public Boolean chromiumSandbox;
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -229,8 +238,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public Boolean headless;
/**
@@ -307,13 +315,6 @@ public interface BrowserType {
this.chromiumSandbox = chromiumSandbox;
return this;
}
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -374,8 +375,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -518,10 +518,6 @@ public interface BrowserType {
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
*/
public Double deviceScaleFactor;
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -577,8 +573,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public Boolean headless;
/**
@@ -856,13 +851,6 @@ public interface BrowserType {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -954,8 +942,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -78,5 +78,12 @@ public interface ConsoleMessage {
* @since v1.8
*/
String type();
/**
* The web worker or service worker that produced this console message, if any. Note that console messages from web workers
* also have non-null {@link com.microsoft.playwright.ConsoleMessage#page ConsoleMessage.page()}.
*
* @since v1.57
*/
Worker worker();
}
@@ -48,6 +48,9 @@ public interface Download {
/**
* Returns a readable stream for a successful download, or throws for a failed/canceled download.
*
* <p> <strong>NOTE:</strong> If you don't need a readable stream, it's usually simpler to read the file from disk after the download completed. See
* {@link com.microsoft.playwright.Download#path Download.path()}.
*
* @since v1.8
*/
InputStream createReadStream();
@@ -170,6 +170,12 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* 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
@@ -244,6 +250,15 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public ClickOptions setSteps(int steps) {
this.steps = steps;
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
@@ -293,6 +308,12 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* 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
@@ -360,6 +381,15 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public DblclickOptions setSteps(int steps) {
this.steps = steps;
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
@@ -563,6 +563,11 @@ public interface Frame {
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public Integer steps;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -617,6 +622,14 @@ public interface Frame {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public DragAndDropOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -245,6 +245,12 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* 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
@@ -320,6 +326,15 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public ClickOptions setSteps(int steps) {
this.steps = steps;
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
@@ -370,6 +385,12 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* 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
@@ -438,6 +459,15 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public DblclickOptions setSteps(int steps) {
this.steps = steps;
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
@@ -494,6 +524,11 @@ public interface Locator {
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public Integer steps;
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -543,6 +578,14 @@ public interface Locator {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public DragToOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -2616,6 +2659,22 @@ public interface Locator {
* @since v1.53
*/
Locator describe(String description);
/**
* Returns locator description previously set with {@link com.microsoft.playwright.Locator#describe Locator.describe()}.
* Returns {@code null} if no custom description has been set.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByRole(AriaRole.BUTTON).describe("Subscribe button");
* System.out.println(button.description()); // "Subscribe button"
*
* Locator input = page.getByRole(AriaRole.TEXTBOX);
* System.out.println(input.description()); // null
* }</pre>
*
* @since v1.57
*/
String description();
/**
* Programmatically dispatch an event on the matching element.
*
@@ -127,12 +127,16 @@ public interface Mouse {
}
class MoveOptions {
/**
* Defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public MoveOptions setSteps(int steps) {
this.steps = steps;
@@ -860,6 +860,11 @@ public interface Page extends AutoCloseable {
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public Integer steps;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -914,6 +919,14 @@ public interface Page extends AutoCloseable {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public DragAndDropOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -5729,6 +5742,20 @@ public interface Page extends AutoCloseable {
* @since v1.8
*/
Keyboard keyboard();
/**
* Returns up to (currently) 200 last console messages from this page. See {@link
* com.microsoft.playwright.Page#onConsoleMessage Page.onConsoleMessage()} for more details.
*
* @since v1.56
*/
List<ConsoleMessage> consoleMessages();
/**
* Returns up to (currently) 200 last page errors from this page. See {@link com.microsoft.playwright.Page#onPageError
* Page.onPageError()} for more details.
*
* @since v1.56
*/
List<String> pageErrors();
/**
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
@@ -6053,6 +6080,21 @@ public interface Page extends AutoCloseable {
* @since v1.9
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* Returns up to (currently) 100 last network request from this page. See {@link com.microsoft.playwright.Page#onRequest
* Page.onRequest()} for more details.
*
* <p> Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory growth
* as new requests come in. Once collected, retrieving most information about the request is impossible.
*
* <p> Note that requests reported through the {@link com.microsoft.playwright.Page#onRequest Page.onRequest()} request are not
* collected, so there is a trade off between efficient memory usage with {@link com.microsoft.playwright.Page#requests
* Page.requests()} and the amount of available information reported through {@link com.microsoft.playwright.Page#onRequest
* Page.onRequest()}.
*
* @since v1.56
*/
List<Request> requests();
/**
* When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to
* automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making them
@@ -370,9 +370,10 @@ public interface Route {
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
* <p> <strong>NOTE:</strong> Some request headers are **forbidden** and cannot be overridden (for example, {@code Cookie}, {@code Host}, {@code
* Content-Length} and others, see <a
* href="https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header">this MDN page</a> for full list). If
* an override is provided for a forbidden header, it will be ignored and the original request header will be used.To set custom cookies, use {@link com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
@@ -402,9 +403,10 @@ public interface Route {
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
* <p> <strong>NOTE:</strong> Some request headers are **forbidden** and cannot be overridden (for example, {@code Cookie}, {@code Host}, {@code
* Content-Length} and others, see <a
* href="https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header">this MDN page</a> for full list). If
* an override is provided for a forbidden header, it will be ignored and the original request header will be used.To set custom cookies, use {@link com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
@@ -44,6 +45,16 @@ public interface Worker {
*/
void offClose(Consumer<Worker> handler);
/**
* Emitted when JavaScript within the worker calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}.
*/
void onConsole(Consumer<ConsoleMessage> handler);
/**
* Removes handler that was previously added with {@link #onConsole onConsole(handler)}.
*/
void offConsole(Consumer<ConsoleMessage> handler);
class WaitForCloseOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
@@ -62,6 +73,35 @@ public interface Worker {
return this;
}
}
class WaitForConsoleMessageOptions {
/**
* Receives the {@code ConsoleMessage} object and resolves to true when the waiting should resolve.
*/
public Predicate<ConsoleMessage> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Receives the {@code ConsoleMessage} object and resolves to true when the waiting should resolve.
*/
public WaitForConsoleMessageOptions setPredicate(Predicate<ConsoleMessage> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForConsoleMessageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Returns the return value of {@code expression}.
*
@@ -158,5 +198,21 @@ public interface Worker {
* @since v1.10
*/
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
/**
* Performs action and waits for a console message.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.57
*/
default ConsoleMessage waitForConsoleMessage(Runnable callback) {
return waitForConsoleMessage(null, callback);
}
/**
* Performs action and waits for a console message.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.57
*/
ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable callback);
}
@@ -989,7 +989,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1034,7 +1034,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1077,7 +1077,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1122,7 +1122,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1165,7 +1165,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1210,7 +1210,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1253,7 +1253,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1298,7 +1298,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // ✓ Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // ✖ Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -62,12 +62,15 @@ abstract class AssertionsBase {
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
if (result.errorMessage != null) {
message += "\n" + result.errorMessage;
}
if (expected == null) {
throw new AssertionFailedError(message + log);
}
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
message += "\nExpected: " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
@@ -34,8 +34,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
@@ -47,7 +46,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final APIRequestContextImpl request;
private final ClockImpl clock;
final List<PageImpl> pages = new ArrayList<>();
final List<PageImpl> backgroundPages = new ArrayList<>();
final Router routes = new Router();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
@@ -82,7 +80,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
enum EventType {
BACKGROUNDPAGE,
CLOSE,
CONSOLE,
DIALOG,
@@ -134,12 +131,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void onBackgroundPage(Consumer<Page> handler) {
listeners.add(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void offBackgroundPage(Consumer<Page> handler) {
listeners.remove(EventType.BACKGROUNDPAGE, handler);
}
@Override
@@ -341,7 +336,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public List<Page> backgroundPages() {
return new ArrayList<>(backgroundPages);
return Collections.emptyList();
}
@Override
@@ -720,10 +715,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
} else if ("backgroundPage".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
backgroundPages.add(page);
listeners.notify(EventType.BACKGROUNDPAGE, page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
@@ -731,9 +722,19 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params);
PageImpl page = null;
if (params.has("page")) {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
}
WorkerImpl worker = null;
if (params.has("worker")) {
worker = connection.getExistingObject(params.getAsJsonObject("worker").get("guid").getAsString());
}
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params, page, worker);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (worker != null) {
worker.listeners.notify(WorkerImpl.EventType.CONSOLE, message);
}
if (page != null) {
page.listeners.notify(PageImpl.EventType.CONSOLE, message);
}
@@ -781,14 +782,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
String errorStr = parseError(params.getAsJsonObject("error"));
PageImpl page;
try {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -20,23 +20,21 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Worker;
import java.util.ArrayList;
import java.util.List;
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
private final PageImpl page;
private final WorkerImpl worker;
private final JsonObject initializer;
public ConsoleMessageImpl(Connection connection, JsonObject initializer) {
public ConsoleMessageImpl(Connection connection, JsonObject initializer, PageImpl page, WorkerImpl worker) {
this.connection = connection;
// Note: currently, we only report console messages for pages and they always have a page.
// However, in the future we might report console messages for service workers or something else,
// where page() would be null.
if (initializer.has("page")) {
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
}
this.page = page;
this.worker = worker;
this.initializer = initializer;
}
@@ -44,6 +42,11 @@ public class ConsoleMessageImpl implements ConsoleMessage {
return initializer.get("type").getAsString();
}
@Override
public Worker worker() {
return worker;
}
public String text() {
return initializer.get("text").getAsString();
}
@@ -229,15 +229,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void click(String selector, ClickOptions options) {
clickImpl(selector, options);
clickImpl(selector, options, null);
}
void clickImpl(String selector, ClickOptions options) {
void clickImpl(String selector, ClickOptions options, Integer steps) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("click", params, timeout(options.timeout));
}
@@ -248,11 +251,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void dblclick(String selector, DblclickOptions options) {
dblclickImpl(selector, options, null);
}
void dblclickImpl(String selector, DblclickOptions options, Integer steps) {
if (options == null) {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("dblclick", params, timeout(options.timeout));
}
@@ -440,16 +450,19 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
dragAndDropImpl(source, target, options);
dragAndDropImpl(source, target, options, null);
}
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
void dragAndDropImpl(String source, String target, DragAndDropOptions options, Integer steps) {
if (options == null) {
options = new DragAndDropOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source);
params.addProperty("target", target);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("dragAndDrop", params, timeout(options.timeout));
}
@@ -627,7 +640,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("key", key);
sendMessage("press", params, timeout(options.timeout));
}
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return selectOptionImpl(selector, values, options);
@@ -16,7 +16,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
@@ -25,6 +24,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
@@ -161,7 +161,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ClickOptions();
}
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
frame.clickImpl(selector, convertType(options, Frame.ClickOptions.class).setStrict(true), options.steps);
}
@Override
@@ -174,12 +174,33 @@ class LocatorImpl implements Locator {
return locator(describeSelector(description));
}
@Override
public String description() {
// Match internal:describe= at the end of the selector with a JSON string
// Pattern matches: >> internal:describe="..." where ... is a JSON-encoded string
Pattern pattern = Pattern.compile(" >> internal:describe=(\"(?:[^\"\\\\]|\\\\.)*\")$");
Matcher matcher = pattern.matcher(selector);
if (matcher.find()) {
String jsonString = matcher.group(1);
try {
// Deserialize the JSON string
return gson().fromJson(jsonString, String.class);
} catch (Exception e) {
// If we can't parse it, return null
return null;
}
}
return null;
}
@Override
public void dblclick(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
frame.dblclick(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true));
frame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true), options.steps);
}
@Override
@@ -197,7 +218,7 @@ class LocatorImpl implements Locator {
}
Frame.DragAndDropOptions frameOptions = convertType(options, Frame.DragAndDropOptions.class);
frameOptions.setStrict(true);
frame.dragAndDrop(selector, ((LocatorImpl) target).selector, frameOptions);
frame.dragAndDropImpl(selector, ((LocatorImpl) target).selector, frameOptions, options.steps);
}
@Override
@@ -627,6 +648,10 @@ class LocatorImpl implements Locator {
@Override
public String toString() {
String description = description();
if (description != null) {
return description;
}
return "Locator@" + selector;
}
@@ -31,6 +31,7 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.parseError;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -236,7 +237,6 @@ public class PageImpl extends ChannelOwner implements Page {
void didClose() {
isClosed = true;
browserContext.pages.remove(this);
browserContext.backgroundPages.remove(this);
listeners.notify(EventType.CLOSE, this);
}
@@ -567,6 +567,17 @@ public class PageImpl extends ChannelOwner implements Page {
return mainFrame.querySelectorAll(selector);
}
@Override
public List<Request> requests() {
JsonObject json = sendMessage("requests", new JsonObject(), NO_TIMEOUT).getAsJsonObject();
JsonArray requests = json.getAsJsonArray("requests");
List<Request> result = new ArrayList<>();
for (JsonElement item : requests) {
result.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return result;
}
@Override
public void addLocatorHandler(Locator locator, Consumer<Locator> handler, AddLocatorHandlerOptions options) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
@@ -677,7 +688,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void click(String selector, ClickOptions options) {
mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class));
mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class), null);
}
@Override
@@ -692,7 +703,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void dblclick(String selector, DblclickOptions options) {
mainFrame.dblclick(selector, convertType(options, Frame.DblclickOptions.class));
mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class), null);
}
@Override
@@ -925,7 +936,10 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class));
if (options == null) {
options = new DragAndDropOptions();
}
mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class), options.steps);
}
@Override
@@ -983,6 +997,29 @@ public class PageImpl extends ChannelOwner implements Page {
return keyboard;
}
@Override
public List<ConsoleMessage> consoleMessages() {
JsonObject json = sendMessage("consoleMessages", new JsonObject(), NO_TIMEOUT).getAsJsonObject();
JsonArray messages = json.getAsJsonArray("messages");
List<ConsoleMessage> result = new ArrayList<>();
for (JsonElement item : messages) {
result.add(new ConsoleMessageImpl(connection, item.getAsJsonObject(), this, null));
}
return result;
}
@Override
public List<String> pageErrors() {
JsonObject json = sendMessage("pageErrors", new JsonObject(), NO_TIMEOUT).getAsJsonObject();
JsonArray errors = json.getAsJsonArray("errors");
List<String> result = new ArrayList<>();
for (JsonElement item : errors) {
String errorStr = parseError(item.getAsJsonObject());
result.add(errorStr);
}
return result;
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return mainFrame.locator(selector, convertType(options, Frame.LocatorOptions.class));
@@ -1358,9 +1395,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Page.waitForLoadState", logger -> {
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
return null;
final LoadState loadState = state == null ? LoadState.LOAD : state;
withTitle("Wait for load state \"" + loadState.toString().toLowerCase() + "\"", () -> {
withWaitLogging("Page.waitForLoadState", logger -> {
mainFrame.waitForLoadStateImpl(loadState, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
return null;
});
});
}
@@ -114,6 +114,7 @@ class FrameExpectOptions {
class FrameExpectResult {
boolean matches;
SerializedValue received;
String errorMessage;
List<String> log;
}
@@ -423,6 +423,18 @@ class Serialization {
return result;
}
static String parseError(JsonObject object) {
SerializedError error = gson().fromJson(object, SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
return errorStr;
}
private static class OptionalSerializer implements JsonSerializer<Optional<?>> {
private static boolean isSupported(Type type) {
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
@@ -18,21 +18,25 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Worker;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static com.microsoft.playwright.impl.Serialization.*;
class WorkerImpl extends ChannelOwner implements Worker {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
final ListenerCollection<EventType> listeners = new ListenerCollection<>();
PageImpl page;
enum EventType {
CLOSE,
CONSOLE,
}
WorkerImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -49,9 +53,19 @@ class WorkerImpl extends ChannelOwner implements Worker {
listeners.remove(EventType.CLOSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
@Override
public void onConsole(Consumer<ConsoleMessage> handler) {
listeners.add(EventType.CONSOLE, handler);
}
@Override
public void offConsole(Consumer<ConsoleMessage> handler) {
listeners.remove(EventType.CONSOLE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
@@ -62,11 +76,23 @@ class WorkerImpl extends ChannelOwner implements Worker {
return withWaitLogging("Worker.waitForClose", logger -> waitForCloseImpl(options, code));
}
@Override
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
return withWaitLogging("Worker.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
}
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
if (options == null) {
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
}
private Worker waitForCloseImpl(WaitForCloseOptions options, Runnable code) {
if (options == null) {
options = new WaitForCloseOptions();
}
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
return waitForEventWithTimeout(EventType.CLOSE, code, null, options.timeout);
}
@Override
@@ -36,7 +36,7 @@ public class PlaywrightExtension implements ParameterResolver {
// There should be at most one instance of PlaywrightRegistry per test run, it keeps
// track of all created Playwright instances and calls `close()` on each of them after
// the tests finished.
static class PlaywrightRegistry implements ExtensionContext.Store.CloseableResource {
static class PlaywrightRegistry implements AutoCloseable {
private final List<Playwright> playwrightList = Collections.synchronizedList(new ArrayList<>());
static synchronized PlaywrightRegistry getOrCreateFor(ExtensionContext extensionContext) {
@@ -59,7 +59,7 @@ public class PlaywrightExtension implements ParameterResolver {
// This is a workaround for JUnit's lack of an "AfterTestRun" hook
// This will be called once after all tests have completed.
@Override
public void close() throws Throwable {
public void close() {
for (Playwright playwright : playwrightList) {
playwright.close();
}
@@ -36,7 +36,7 @@ package com.microsoft.playwright.junit;
* public Options getOptions() {
* return new Options()
* .setHeadless(false)
* .setContextOption(new Browser.NewContextOptions()
* .setContextOptions(new Browser.NewContextOptions()
* .setBaseURL("https://github.com"))
* .setApiRequestOptions(new APIRequest.NewContextOptions()
* .setBaseURL("https://playwright.dev"));
@@ -20,16 +20,16 @@ public class Cookie {
public String name;
public String value;
/**
* Either url or domain / path are required. Optional.
* Either {@code url} or both {@code domain} and {@code path} are required. Optional.
*/
public String url;
/**
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or
* domain / path are required. Optional.
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either {@code
* url} or both {@code domain} and {@code path} are required. Optional.
*/
public String domain;
/**
* Either url or domain / path are required Optional.
* Either {@code url} or both {@code domain} and {@code path} are required. Optional.
*/
public String path;
/**
@@ -60,22 +60,22 @@ public class Cookie {
this.value = value;
}
/**
* Either url or domain / path are required. Optional.
* Either {@code url} or both {@code domain} and {@code path} are required. Optional.
*/
public Cookie setUrl(String url) {
this.url = url;
return this;
}
/**
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or
* domain / path are required. Optional.
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either {@code
* url} or both {@code domain} and {@code path} are required. Optional.
*/
public Cookie setDomain(String domain) {
this.domain = domain;
return this;
}
/**
* Either url or domain / path are required Optional.
* Either {@code url} or both {@code domain} and {@code path} are required. Optional.
*/
public Cookie setPath(String path) {
this.path = path;
@@ -0,0 +1,46 @@
/*
* 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 org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestExpectMisc extends TestBase {
@Test
void strictModeViolationErrorFormat() {
page.setContent(" <div>hello</div><div>hi</div>");
AssertionFailedError error = assertThrows(AssertionFailedError.class, () ->
assertThat(page.locator("div")).isVisible());
assertTrue(error.getMessage().contains("Locator expected to be visible"), error.getMessage());
assertTrue(error.getMessage().contains("Error: strict mode violation: locator(\"div\") resolved to 2 elements:"), error.getMessage());
}
@Test
void invalidSelectorErrorFormat() {
page.setContent("<div>hello</div><div>hi</div>");
AssertionFailedError error = assertThrows(AssertionFailedError.class, () ->
assertThat(page.locator("##")).isVisible());
assertTrue(error.getMessage().contains("Locator expected to be visible"), error.getMessage());
assertTrue(error.getMessage().contains("Error: Unexpected token \"#\" while parsing css selector \"##\"."), error.getMessage());
}
}
@@ -223,7 +223,7 @@ public class TestLocatorAssertions extends TestBase {
});
assertEquals("[Text 1, Text 3, Extra]", e.getExpected().getStringRepresentation());
assertEquals("[Text 1, Text 3]", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have text: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have text\nExpected: [Text 1, Text 3, Extra]"), e.getMessage());
assertTrue(e.getMessage().contains("Received: [Text 1, Text 3]"), e.getMessage());
}
@@ -272,7 +272,7 @@ public class TestLocatorAssertions extends TestBase {
});
assertEquals("foo", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id': foo\nReceived: node"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id'\nExpected: foo\nReceived: node"), e.getMessage());
}
@Test
@@ -291,7 +291,7 @@ public class TestLocatorAssertions extends TestBase {
});
assertEquals(".Nod..", e.getExpected().getStringRepresentation());
assertEquals("node", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex: .Nod..\nReceived: node"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to have attribute 'id' matching regex\nExpected: .Nod..\nReceived: node"), e.getMessage());
}
@Test
@@ -629,7 +629,7 @@ public class TestLocatorAssertions extends TestBase {
" </select>");
Locator locator = page.locator("select");
locator.selectOption(new String[] {"B"});
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")});
});
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
@@ -639,7 +639,7 @@ public class TestLocatorAssertions extends TestBase {
void hasValuesFailsWhenNotASelectElement() {
page.setContent("<input value=\"foo\" />");
Locator locator = page.locator("input");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(locator).hasValues(new Pattern[]{ Pattern.compile("R"), Pattern.compile("G")}, new LocatorAssertions.HasValuesOptions().setTimeout(1000));
});
assertTrue(e.getMessage().contains("Not a select element with a multiple attribute"), e.getMessage());
@@ -661,7 +661,7 @@ public class TestLocatorAssertions extends TestBase {
});
assertEquals("checked", e.getExpected().getStringRepresentation());
assertEquals("unchecked", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected to be: checked"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected to be\nExpected: checked"), e.getMessage());
}
@Test
@@ -674,7 +674,7 @@ public class TestLocatorAssertions extends TestBase {
assertEquals("checked", e.getExpected().getStringRepresentation());
assertEquals("checked", e.getActual().getStringRepresentation());
assertTrue(e.getMessage().contains("Locator expected not to be: checked"), e.getMessage());
assertTrue(e.getMessage().contains("Locator expected not to be\nExpected: checked"), e.getMessage());
}
@Test
@@ -690,7 +690,7 @@ public class TestLocatorAssertions extends TestBase {
Locator locator = page.locator("input");
AssertionFailedError error = assertThrows(AssertionFailedError.class,
() -> assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setChecked(false).setTimeout(1000)));
assertTrue(error.getMessage().contains("Locator expected to be: unchecked"), error.getMessage());
assertTrue(error.getMessage().contains("Locator expected to be\nExpected: unchecked"), error.getMessage());
}
@Test
@@ -796,7 +796,7 @@ public class TestLocatorAssertions extends TestBase {
void isEditableThrowsOnNonInputElement() {
page.setContent("<button>");
Locator locator = page.locator("button");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> assertThat(locator).isEditable());
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(locator).isEditable());
assertTrue(e.getMessage().contains("Element is not an <input>, <textarea>, <select> or [contenteditable] and does not have a role allowing [aria-readonly]"), e.getMessage());
}
@@ -1,4 +1,4 @@
/*
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -206,7 +206,7 @@ public class TestLocatorAssertions2 extends TestBase {
page.setContent("<input type=checkbox></input>");
page.locator("input").evaluate("e => e.indeterminate = true");
Locator locator = page.locator("input");
PlaywrightException e = assertThrows(PlaywrightException.class, () ->
AssertionFailedError e = assertThrows(AssertionFailedError.class, () ->
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setIndeterminate(true).setChecked(false)));
assertTrue(e.getMessage().contains("Can't assert indeterminate and checked at the same time"), e.getMessage());
}
@@ -220,4 +220,5 @@ public class TestLocatorAssertions2 extends TestBase {
// TODO: should be "assertThat().isChecked() with timeout 1000ms"
assertTrue(e.getMessage().contains("Assert \"isChecked\" with timeout 1000ms"), e.getMessage());
}
}
@@ -65,4 +65,41 @@ public class TestLocatorClick extends TestBase {
page.getByText("Go").click(new Locator.ClickOptions().setModifiers(asList(KeyboardModifier.CONTROLORMETA))));
assertThat(newPage).hasURL(server.PREFIX + "/title.html");
}
@Test
void shouldClickWithTweenedMouseMovement() {
page.setContent(
"<body style=\"margin: 0; padding: 0; height: 500px; width: 500px;\">\n" +
" <div style=\"position: relative; top: 280px; left: 150px; width: 100px; height: 40px\">Click me</div>\n" +
"</body>"
);
// The test becomes flaky on WebKit without next line.
if (isWebKit()) {
page.evaluate("() => new Promise(requestAnimationFrame)");
}
page.mouse().move(100, 100);
page.evaluate("() => {\n" +
" window['result'] = [];\n" +
" document.addEventListener('mousemove', event => {\n" +
" window['result'].push([event.clientX, event.clientY]);\n" +
" });\n" +
"}");
// Centerpoint at 150 + 100/2, 280 + 40/2 = 200, 300
page.locator("div").click(new Locator.ClickOptions().setSteps(5));
assertEquals(
asList(
asList(120, 140),
asList(140, 180),
asList(160, 220),
asList(180, 260),
asList(200, 300)
),
page.evaluate("result")
);
}
}
@@ -186,4 +186,53 @@ public class TestLocatorConvenience extends TestBase {
page.setContent("<div>A</div><div>B</div><div>C</div>");
assertEquals(asList("A", "B", "C"), page.locator("div").allInnerTexts());
}
@Test
void descriptionShouldReturnNullForUndescribedLocators() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div");
assertNull(locator.description());
}
@Test
void descriptionShouldReturnSimpleDescription() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div").describe("my div");
assertEquals("my div", locator.description());
}
@Test
void descriptionShouldHandleSpecialCharacters() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div").describe("título 😊");
assertEquals("título 😊", locator.description());
}
@Test
void descriptionShouldWorkWithChainedLocators() {
page.setContent("<div><span>Hello</span></div>");
Locator locator = page.locator("div").describe("container").locator("span");
assertNull(locator.description());
}
@Test
void descriptionShouldReturnLastDescriptionForMultipleCalls() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div").describe("first").describe("second");
assertEquals("second", locator.description());
}
@Test
void toStringShouldReturnFormattedLocator() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div");
assertTrue(locator.toString().startsWith("Locator@"));
}
@Test
void toStringShouldPreferDescription() {
page.setContent("<div>Hello</div>");
Locator locator = page.locator("div").describe("my div");
assertEquals("my div", locator.toString());
}
}
@@ -127,4 +127,13 @@ public class TestPageAriaSnapshot {
" - listitem: \"1.2\"");
});
}
@Test
void matchValuesBothAgainstRegexAndString(Page page) {
page.setContent("<a href=\"/auth?r=/\">Log in</a>");
checkAndMatchSnapshot(page.locator("body"),
"- link \"Log in\":\n" +
" - /url: /auth?r=/");
}
}
@@ -107,7 +107,7 @@ public class TestPageAssertions extends TestBase {
});
assertEquals("foo", e.getExpected().getValue());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to be: foo\nReceived: Woof-Woof"), e.getMessage());
assertTrue(e.getMessage().contains("Page title expected to be\nExpected: foo\nReceived: Woof-Woof"), e.getMessage());
}
@Test
@@ -124,7 +124,7 @@ public class TestPageAssertions extends TestBase {
});
assertEquals("^foo[AB]", e.getExpected().getStringRepresentation());
assertEquals("Woof-Woof", e.getActual().getValue());
assertTrue(e.getMessage().contains("Page title expected to match regex: ^foo[AB]\nReceived: Woof-Woof"), e.getMessage());
assertTrue(e.getMessage().contains("Page title expected to match regex\nExpected: ^foo[AB]\nReceived: Woof-Woof"), e.getMessage());
}
@Test
@@ -124,4 +124,94 @@ public class TestPageDrag extends TestBase {
page.locator("#source").dragTo(page.locator("#target"));
assertEquals(true, page.evalOnSelector("#target", "target => target.contains(document.querySelector('#source'))"));
}
@Test
void shouldDragAndDropWithTweenedMouseMovement() {
page.setContent(
"<body style='margin: 0; padding: 0;'>\n" +
" <div style='width:100px;height:100px;background:red;' id='red'></div>\n" +
" <div style='width:300px;height:100px;background:blue;' id='blue'></div>\n" +
"</body>"
);
JSHandle eventsHandle = page.evaluateHandle("() => {\n" +
" const events = [];\n" +
" document.addEventListener('mousedown', event => {\n" +
" events.push({ type: 'mousedown', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" document.addEventListener('mouseup', event => {\n" +
" events.push({ type: 'mouseup', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" document.addEventListener('mousemove', event => {\n" +
" events.push({ type: 'mousemove', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" return events;\n" +
"}");
// Red div center is at (50, 50), blue div center is at (150, 50)
// With 4 steps, we expect intermediate positions at (75, 50), (100, 50), (125, 50)
page.dragAndDrop("#red", "#blue", new Page.DragAndDropOptions().setSteps(4));
Object json = eventsHandle.jsonValue();
// Expected sequence: mousemove to (50,50), mousedown at (50,50),
// then 3 mousemove events at (75,50), (100,50), (125,50),
// and mouseup at (150,50)
assertJsonEquals(
"[" +
"{type: \"mousemove\", x: 50, y: 50}," +
"{type: \"mousedown\", x: 50, y: 50}," +
"{type: \"mousemove\", x: 75, y: 75}," +
"{type: \"mousemove\", x: 100, y: 100}," +
"{type: \"mousemove\", x: 125, y: 125}," +
"{type: \"mousemove\", x: 150, y: 150}," +
"{type: \"mouseup\", x: 150, y: 150}" +
"]",
json
);
}
@Test
void shouldDragToWithTweenedMouseMovement() {
page.setContent(
"<body style='margin: 0; padding: 0;'>\n" +
" <div style='width:100px;height:100px;background:red;' id='red'></div>\n" +
" <div style='width:300px;height:100px;background:blue;' id='blue'></div>\n" +
"</body>"
);
JSHandle eventsHandle = page.evaluateHandle("() => {\n" +
" const events = [];\n" +
" document.addEventListener('mousedown', event => {\n" +
" events.push({ type: 'mousedown', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" document.addEventListener('mouseup', event => {\n" +
" events.push({ type: 'mouseup', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" document.addEventListener('mousemove', event => {\n" +
" events.push({ type: 'mousemove', x: event.pageX, y: event.pageY });\n" +
" });\n" +
" return events;\n" +
"}");
// Red div center is at (50, 50), blue div center is at (150, 50)
// With 4 steps, we expect intermediate positions at (75, 50), (100, 50), (125, 50)
page.locator("#red").dragTo(page.locator("#blue"), new Locator.DragToOptions().setSteps(4));
Object json = eventsHandle.jsonValue();
// Expected sequence: mousemove to (50,50), mousedown at (50,50),
// then 3 mousemove events at (75,50), (100,50), (125,50),
// and mouseup at (150,50)
assertJsonEquals(
"[" +
"{type: \"mousemove\", x: 50, y: 50}," +
"{type: \"mousedown\", x: 50, y: 50}," +
"{type: \"mousemove\", x: 75, y: 75}," +
"{type: \"mousemove\", x: 100, y: 100}," +
"{type: \"mousemove\", x: 125, y: 125}," +
"{type: \"mousemove\", x: 150, y: 150}," +
"{type: \"mouseup\", x: 150, y: 150}" +
"]",
json
);
}
}
@@ -103,7 +103,7 @@ public class TestPageEventConsole extends TestBase {
ConsoleMessage message = page.waitForConsoleMessage(() -> {
page.evaluate("async url => fetch(url).catch(e => {})", server.EMPTY_PAGE);
});
assertTrue(message.text().contains("Access-Control-Allow-Origin"));
assertTrue(message.text().contains("Access-Control-Allow-Origin") || message.text().contains("blocked by CORS policy"), message.text());
assertEquals("error", message.type());
}
@@ -130,4 +130,23 @@ public class TestPageEventConsole extends TestBase {
assertEquals("2", message.text());
assertEquals("info", message.type());
}
@Test
void consoleMessagesShouldWork() {
page.evaluate("() => {\n" +
" for (let i = 0; i < 301; i++)\n" +
" console.log('message' + i);\n" +
"}");
List<ConsoleMessage> messages = page.consoleMessages();
assertTrue(messages.size() >= 100, "should be at least 100 messages");
int firstIndex = messages.size() - 100;
for (int i = 0; i < 100; i++) {
ConsoleMessage message = messages.get(firstIndex + i);
assertEquals("message" + (201 + i), message.text());
assertEquals("log", message.type());
assertEquals(page, message.page());
}
}
}
@@ -0,0 +1,47 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestPageEventPageError extends TestBase {
@Test
void pageErrorsShouldWork() {
page.navigate(server.EMPTY_PAGE);
page.evaluate("async () => {\n" +
" for (let i = 0; i < 301; i++)\n" +
" window.setTimeout(() => { throw new Error('error' + i); }, 0);\n" +
" await new Promise(f => window.setTimeout(f, 100));\n" +
" }");
List<String> errors = page.pageErrors();
assertTrue(errors.size() >= 100, "should be at least 100 errors");
// Check the last 100 errors (indices 201-300)
int firstIndex = errors.size() - 100;
for (int i = 0; i < 100; i++) {
String error = errors.get(firstIndex + i);
assertTrue(error.startsWith("Error: error" + (201 + i)), error);
}
}
}
@@ -0,0 +1,66 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageEventRequest extends TestBase {
@Test
void shouldReturnLastRequests() throws ExecutionException, InterruptedException {
page.navigate(server.PREFIX + "/title.html");
// Set up routes for 200 requests
for (int i = 0; i < 200; ++i) {
final int index = i;
server.setRoute("/fetch-" + i, exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().write(("url:" + server.PREFIX + exchange.getRequestURI().toString()).getBytes());
exchange.getResponseBody().close();
});
}
// #0 is the navigation request, so start with #1.
for (int i = 0; i < 99; ++i) {
page.evaluate("url => fetch(url)", server.PREFIX + "/fetch-" + i);
}
List<Request> first99Requests = page.requests();
first99Requests.remove(0); // Remove the navigation request
for (int i = 99; i < 199; ++i) {
page.evaluate("url => fetch(url)", server.PREFIX + "/fetch-" + i);
}
List<Request> last100Requests = page.requests();
List<Request> allRequests = new ArrayList<>();
allRequests.addAll(first99Requests);
allRequests.addAll(last100Requests);
// All 199 requests are fully functional.
int index = 0;
for (Request request : allRequests) {
Response response = request.response();
assertEquals("url:" + server.PREFIX + "/fetch-" + index, response.text());
assertEquals(server.PREFIX + "/fetch-" + index, request.url());
index++;
}
}
}
@@ -27,7 +27,7 @@ import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageInterception extends TestBase {
public class TestPageInterception extends TestBase {
@Test
void shouldWorkWithNavigationSmoke() {
HashMap<String, Request> requests = new HashMap<>();
@@ -161,6 +161,16 @@ public class TestPageInterception extends TestBase {
assertFalse(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidc/foo").find());
assertTrue(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidcnice").find());
assertTrue(globToRegex("**/*.js").matcher("/foo.js").find());
assertFalse(globToRegex("asd/**.js").matcher("/foo.js").find());
assertFalse(globToRegex("**/*.js").matcher("bar_foo.js").find());
// custom protocols
assertTrue(globToRegex("my.custom.protocol://**").matcher("my.custom.protocol://foo").find());
assertFalse(globToRegex("my.{p,y}://**").matcher("my.p://foo").find());
assertTrue(globToRegex("my.{p,y}://**").matcher("my.p://foo/").find());
assertTrue(globToRegex("f*e://**").matcher("file:///foo/").find());
// range [] is NOT supported
assertTrue(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/v[0-9]").find());
assertFalse(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/version").find());
@@ -186,6 +196,10 @@ public class TestPageInterception extends TestBase {
assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
// /**/ should match /.
assertTrue(urlMatches(null, "https://foo/bar.js", "https://foo/**/bar.js"));
assertTrue(urlMatches(null, "https://foo/bar.js", "https://foo/**/**/bar.js"));
// Case insensitive matching
assertTrue(urlMatches(null, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
assertTrue(urlMatches("http://ignored", "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
@@ -193,6 +207,10 @@ public class TestPageInterception extends TestBase {
assertFalse(urlMatches(null, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR"));
assertFalse(urlMatches(null, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B"));
assertTrue(urlMatches(null, "https://localhost:3000/?a=b", "**/?a=b"));
assertTrue(urlMatches(null, "https://localhost:3000/?a=b", "**?a=b"));
assertTrue(urlMatches(null, "https://localhost:3000/?a=b", "**=b"));
// This is not supported, we treat ? as a query separator.
assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js"));
assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev"));
@@ -79,14 +79,8 @@ public class TestScreencast extends TestBase {
if (!popup.isClosed()) {
popup.waitForClose(() -> {});
}
// WebKit pauses renderer before win.close() and actually writes something.
if (isWebKit()) {
popup.video().saveAs(saveAsPath);
assertTrue(Files.exists(saveAsPath));
} else {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> popup.video().saveAs(saveAsPath));
assertTrue(e.getMessage().contains("Page did not produce any video frames"), e.getMessage());
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> popup.video().saveAs(saveAsPath));
assertTrue(e.getMessage().contains("Page did not produce any video frames"), e.getMessage());
}
}
@@ -359,4 +359,24 @@ public class TestTracing extends TestBase {
});
});
}
@Test
public void shouldShowWaitForLoadState(@TempDir Path tempDir) throws Exception {
// https://github.com/microsoft/playwright/issues/37297
context.tracing().start(new Tracing.StartOptions());
page.navigate(server.EMPTY_PAGE);
page.waitForLoadState();
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Wait for load state \"load\""),
});
});
}
}
@@ -193,8 +193,31 @@ public class TestWorkers extends TestBase {
page.navigate(server.EMPTY_PAGE);
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals("10\u00A0000,2", worker.evaluate("() => (10000.20).toLocaleString()"));
// https://github.com/microsoft/playwright/issues/38919
String expected = isFirefox() ? "10,000.2" : "10\u00A0000,2";
assertEquals(expected, worker.evaluate("() => (10000.20).toLocaleString()"));
context.close();
}
@Test
void shouldReportConsoleEventOnTheWorker() {
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => { window.worker = new Worker(URL.createObjectURL(new Blob(['42'], {type: 'application/javascript'}))); }"
));
ConsoleMessage[] message2 = {null};
ConsoleMessage[] message3 = {null};
page.onConsoleMessage(msg -> message2[0] = msg);
page.context().onConsoleMessage(msg -> message3[0] = msg);
ConsoleMessage message1 = worker.waitForConsoleMessage(() -> {
worker.evaluate("() => console.log('hello from worker')");
});
assertEquals("hello from worker", message1.text());
assertSame(message1, message2[0]);
assertSame(message1, message3[0]);
}
}
@@ -37,24 +37,12 @@ import java.util.zip.ZipInputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Utils {
private static final AtomicInteger nextUnusedPort = new AtomicInteger(9000);
private static boolean available(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return true;
} catch (IOException ignored) {
return false;
}
}
public static int nextFreePort() {
for (int i = 0; i < 100; i++) {
int port = nextUnusedPort.getAndIncrement();
if (available(port)) {
return port;
}
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
} catch (IOException e) {
throw new RuntimeException("Cannot find free port", e);
}
throw new RuntimeException("Cannot find free port: " + nextUnusedPort.get());
}
static void assertJsonEquals(Object expected, Object actual) {
@@ -39,7 +39,8 @@ public class TestFixtureDeviceOption {
public void testPredefinedDeviceParameters(Server server, Page page) {
page.navigate(server.EMPTY_PAGE);
assertEquals("webkit", page.context().browser().browserType().name());
assertEquals(3, page.evaluate("window.devicePixelRatio"));
// TODO: failing since 1.57 roll.
// assertEquals(3, page.evaluate("window.devicePixelRatio"));
assertEquals(980, page.evaluate("window.innerWidth"));
assertEquals(1668, page.evaluate("window.innerHeight"));
}
+11 -11
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.55.0</version>
<version>1.58.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
@@ -44,8 +44,8 @@
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
<gson.version>2.12.1</gson.version>
<junit.version>5.12.1</junit.version>
<gson.version>2.13.2</gson.version>
<junit.version>5.14.1</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<websocket.version>1.6.0</websocket.version>
<slf4j.version>2.0.17</slf4j.version>
@@ -118,17 +118,17 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.1</version>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<version>3.4.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -143,12 +143,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<version>3.4.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<version>3.12.0</version>
<configuration>
<additionalOptions>--allow-script-in-comments</additionalOptions>
<failOnError>false</failOnError>
@@ -159,7 +159,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
<version>3.5.4</version>
<configuration>
<properties>
<configurationParameters>
@@ -181,12 +181,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
<version>3.2.8</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
+1 -1
View File
@@ -1 +1 @@
1.55.0
1.58.0
+1 -1
View File
@@ -39,7 +39,7 @@ do
cd $PLATFORM
echo "Downloading driver for $PLATFORM to $(pwd)"
URL=https://playwright.azureedge.net/builds/driver
URL=https://cdn.playwright.dev/builds/driver
if [[ "$DRIVER_VERSION" == *-alpha* || "$DRIVER_VERSION" == *-beta* || "$DRIVER_VERSION" == *-next* ]]; then
URL=$URL/next
fi
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.55.0</version>
<version>1.58.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
@@ -1011,7 +1011,7 @@ class Interface extends TypeDefinition {
output.add("import java.util.function.BooleanSupplier;");
}
if (asList("Page", "Frame", "BrowserContext", "WebSocket").contains(jsonName)) {
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").contains(jsonName)) {
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.playwright</groupId>
<artifactId>test-cli-fatjar</artifactId>
<version>1.55.0</version>
<version>1.58.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.55.0</version>
<version>1.58.0</version>
<name>Test Playwright Command Line Version</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-local-installation</artifactId>
<version>1.55.0</version>
<version>1.58.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.55.0</version>
<version>1.58.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.55.0</version>
<version>1.58.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on