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

Compare commits

...

16 Commits

Author SHA1 Message Date
Yury Semikhatsky 80911c6a2a chore: mark version 1.40.0 (#1427) (#1429) 2023-11-20 11:36:57 -08:00
Yury Semikhatsky d91c93bed1 chore: roll 1.40.0 (#1426) 2023-11-20 10:19:23 -08:00
Yury Semikhatsky 2b45615e14 chore: roll driver to 1.40.0-beta-1700102862000 (#1424) 2023-11-16 10:16:15 -08:00
Max Schmitt d6f6448b1d chore: fix Ubuntu 22.04 WebKit on 20.04 host (#1423) 2023-11-15 19:54:37 +01:00
Yury Semikhatsky 5d9f76f4c5 chore: roll driver to 11/14/23, revert expect change (#1422) 2023-11-14 12:01:12 -08:00
Yury Semikhatsky 38f3dd3f5a chore: roll driver to 1.40.0-alpha-nov-13-2023 (#1420) 2023-11-14 10:24:07 -08:00
Yury Semikhatsky 4efb792c36 chore: set dev version to 1.40 (#1411) 2023-10-13 14:02:10 -07:00
Jayapraveen Arcot aa4f9754de docs: update the system requirements link in readme (#1408) 2023-10-13 11:46:20 -07:00
Yury Semikhatsky 7d9d9a2d9d chore: roll driver to 1.39.0 (#1409) 2023-10-13 11:45:23 -07:00
Yury Semikhatsky db1c899cf6 chore: unflake TestBrowserContextProxy.shouldExcludePatterns (#1404) 2023-10-11 15:23:46 -07:00
Yury Semikhatsky 91e70280a3 chore: update driver to 1.39.0-beta (Oct 11) (#1403) 2023-10-11 12:39:37 -07:00
Yury Semikhatsky 5fe5a3e925 Revert "feat(junit-playwright) (#1371)" (#1402)
This reverts commit fb2188cd2a.
2023-10-10 16:32:08 -07:00
uchagani fb2188cd2a feat(junit-playwright) (#1371) 2023-09-29 16:41:01 -07:00
Yury Semikhatsky fc4a536308 chore: bump maven version (#1392) 2023-09-20 11:39:56 -07:00
Yury Semikhatsky 4312a98ae0 test: unflake cookie roundtrip (#1388) 2023-09-18 20:44:51 -07:00
Yury Semikhatsky f629f915de chore: set dev version to 1.39.0-SNAPSHOT (#1387) 2023-09-18 17:32:19 -07:00
64 changed files with 817 additions and 221 deletions
+4 -4
View File
@@ -11,11 +11,11 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->117.0.5938.62<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->117.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->120.0.6099.28<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->119.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.38.0-SNAPSHOT</version>
<version>1.40.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.38.0-SNAPSHOT</version>
<version>1.40.0</version>
</parent>
<artifactId>driver</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.38.0-SNAPSHOT</version>
<version>1.40.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.38.0-SNAPSHOT</version>
<version>1.40.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -80,8 +80,8 @@ public interface APIRequestContext {
APIResponse delete(String url, RequestOptions params);
/**
* All responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar methods are stored in the
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}. This method discards all stored
* responses, and makes {@link APIResponse#body APIResponse.body()} throw "Response disposed" error.
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}.This method discards all its resources,
* calling any method on disposed {@code APIRequestContext} will throw an exception.
*
* @since v1.16
*/
@@ -56,6 +56,20 @@ public interface Browser extends AutoCloseable {
*/
void offDisconnected(Consumer<Browser> handler);
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the browser closure.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the browser closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
class NewContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
@@ -1165,7 +1179,25 @@ public interface Browser extends AutoCloseable {
*
* @since v1.8
*/
void close();
default void close() {
close(null);
}
/**
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
*
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link BrowserContext#close
* BrowserContext.close()} on any {@code BrowserContext}'s you explicitly created earlier with {@link Browser#newContext
* Browser.newContext()} **before** calling {@link Browser#close Browser.close()}.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*
* @since v1.8
*/
void close(CloseOptions options);
/**
* Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
*
@@ -186,6 +186,20 @@ public interface BrowserContext extends AutoCloseable {
*/
void offResponse(Consumer<Response> handler);
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the context closure.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the context closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@@ -504,7 +518,17 @@ public interface BrowserContext extends AutoCloseable {
*
* @since v1.8
*/
void close();
default void close() {
close(null);
}
/**
* Closes the browser context. All the pages that belong to the browser context will be closed.
*
* <p> <strong>NOTE:</strong> The default browser context cannot be closed.
*
* @since v1.8
*/
void close(CloseOptions options);
/**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs
* are returned.
@@ -486,6 +486,11 @@ public interface BrowserType {
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*/
public Map<String, Object> firefoxUserPrefs;
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
@@ -786,6 +791,14 @@ public interface BrowserType {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*/
public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
@@ -45,7 +45,7 @@ public interface Download {
*/
void cancel();
/**
* Returns readable stream for current download or {@code null} if download failed.
* Returns a readable stream for a successful download, or throws for a failed/canceled download.
*
* @since v1.8
*/
@@ -69,8 +69,8 @@ public interface Download {
*/
Page page();
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
* Returns path to the downloaded file for a successful download, or throws for a failed/canceled download. The method will
* wait for the download to finish if necessary. The method throws when connected remotely.
*
* <p> Note that the download's file name is a random GUID, use {@link Download#suggestedFilename Download.suggestedFilename()}
* to get suggested file name.
@@ -1411,13 +1411,17 @@ public interface ElementHandle extends JSHandle {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -1451,13 +1455,17 @@ public interface ElementHandle extends JSHandle {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -1878,7 +1886,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1909,7 +1917,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1938,7 +1946,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1969,7 +1977,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1998,7 +2006,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2029,7 +2037,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2058,7 +2066,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2089,7 +2097,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2118,7 +2126,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2149,7 +2157,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2178,7 +2186,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2209,7 +2217,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2582,13 +2582,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2624,13 +2628,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2665,13 +2673,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -4058,7 +4070,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4091,7 +4103,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4122,7 +4134,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4155,7 +4167,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4186,7 +4198,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4219,7 +4231,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4250,7 +4262,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4283,7 +4295,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4314,7 +4326,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4347,7 +4359,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4378,7 +4390,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4411,7 +4423,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -2096,6 +2096,10 @@ public interface Locator {
/**
* Returns an array of {@code node.innerText} values for all matching nodes.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allInnerTexts();
@@ -2107,6 +2111,9 @@ public interface Locator {
/**
* Returns an array of {@code node.textContent} values for all matching nodes.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allTextContents();
@@ -2380,6 +2387,10 @@ public interface Locator {
/**
* Returns the number of elements matching the locator.
*
* <p> <strong>NOTE:</strong> If you need to assert the number of elements on the page, prefer {@link LocatorAssertions#hasCount
* LocatorAssertions.hasCount()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* int count = page.getByRole(AriaRole.LISTITEM).count();
@@ -2460,13 +2471,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2505,13 +2520,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2549,13 +2568,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2986,6 +3009,10 @@ public interface Locator {
/**
* Returns the matching element's attribute value.
*
* <p> <strong>NOTE:</strong> If you need to assert an element's attribute, prefer {@link LocatorAssertions#hasAttribute
* LocatorAssertions.hasAttribute()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @param name Attribute name to get the value for.
* @since v1.14
*/
@@ -2995,6 +3022,10 @@ public interface Locator {
/**
* Returns the matching element's attribute value.
*
* <p> <strong>NOTE:</strong> If you need to assert an element's attribute, prefer {@link LocatorAssertions#hasAttribute
* LocatorAssertions.hasAttribute()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @param name Attribute name to get the value for.
* @since v1.14
*/
@@ -3634,6 +3665,10 @@ public interface Locator {
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText">{@code
* element.innerText}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* @since v1.14
*/
default String innerText() {
@@ -3643,12 +3678,19 @@ public interface Locator {
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText">{@code
* element.innerText}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* @since v1.14
*/
String innerText(InnerTextOptions options);
/**
* Returns the value for the matching {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> <strong>NOTE:</strong> If you need to assert input value, prefer {@link LocatorAssertions#hasValue LocatorAssertions.hasValue()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String value = page.getByRole(AriaRole.TEXTBOX).inputValue();
@@ -3669,6 +3711,9 @@ public interface Locator {
/**
* Returns the value for the matching {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> <strong>NOTE:</strong> If you need to assert input value, prefer {@link LocatorAssertions#hasValue LocatorAssertions.hasValue()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String value = page.getByRole(AriaRole.TEXTBOX).inputValue();
@@ -3687,6 +3732,10 @@ public interface Locator {
/**
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
*
* <p> <strong>NOTE:</strong> If you need to assert that checkbox is checked, prefer {@link LocatorAssertions#isChecked LocatorAssertions.isChecked()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean checked = page.getByRole(AriaRole.CHECKBOX).isChecked();
@@ -3700,6 +3749,10 @@ public interface Locator {
/**
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
*
* <p> <strong>NOTE:</strong> If you need to assert that checkbox is checked, prefer {@link LocatorAssertions#isChecked LocatorAssertions.isChecked()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean checked = page.getByRole(AriaRole.CHECKBOX).isChecked();
@@ -3712,6 +3765,10 @@ public interface Locator {
* Returns whether the element is disabled, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is disabled, prefer {@link LocatorAssertions#isDisabled
* LocatorAssertions.isDisabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean disabled = page.getByRole(AriaRole.BUTTON).isDisabled();
@@ -3726,6 +3783,10 @@ public interface Locator {
* Returns whether the element is disabled, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is disabled, prefer {@link LocatorAssertions#isDisabled
* LocatorAssertions.isDisabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean disabled = page.getByRole(AriaRole.BUTTON).isDisabled();
@@ -3737,6 +3798,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link LocatorAssertions#isEditable
* LocatorAssertions.isEditable()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean editable = page.getByRole(AriaRole.TEXTBOX).isEditable();
@@ -3750,6 +3815,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link LocatorAssertions#isEditable
* LocatorAssertions.isEditable()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean editable = page.getByRole(AriaRole.TEXTBOX).isEditable();
@@ -3761,6 +3830,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is enabled, prefer {@link LocatorAssertions#isEnabled
* LocatorAssertions.isEnabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean enabled = page.getByRole(AriaRole.BUTTON).isEnabled();
@@ -3774,6 +3847,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is enabled, prefer {@link LocatorAssertions#isEnabled
* LocatorAssertions.isEnabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean enabled = page.getByRole(AriaRole.BUTTON).isEnabled();
@@ -3786,6 +3863,9 @@ public interface Locator {
* Returns whether the element is hidden, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is hidden, prefer {@link LocatorAssertions#isHidden LocatorAssertions.isHidden()} to
* avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean hidden = page.getByRole(AriaRole.BUTTON).isHidden();
@@ -3800,6 +3880,9 @@ public interface Locator {
* Returns whether the element is hidden, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is hidden, prefer {@link LocatorAssertions#isHidden LocatorAssertions.isHidden()} to
* avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean hidden = page.getByRole(AriaRole.BUTTON).isHidden();
@@ -3811,6 +3894,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is visible, prefer {@link LocatorAssertions#isVisible LocatorAssertions.isVisible()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean visible = page.getByRole(AriaRole.BUTTON).isVisible();
@@ -3824,6 +3911,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is visible, prefer {@link LocatorAssertions#isVisible LocatorAssertions.isVisible()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean visible = page.getByRole(AriaRole.BUTTON).isVisible();
@@ -4951,6 +5042,9 @@ public interface Locator {
/**
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">{@code node.textContent}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @since v1.14
*/
default String textContent() {
@@ -4959,6 +5053,9 @@ public interface Locator {
/**
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">{@code node.textContent}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @since v1.14
*/
String textContent(TextContentOptions options);
@@ -650,12 +650,23 @@ public interface Page extends AutoCloseable {
}
}
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the page closure.
*/
public String reason;
/**
* Defaults to {@code false}. Whether to run the <a
* href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">before unload</a> page handlers.
*/
public Boolean runBeforeUnload;
/**
* The reason to be reported to the operations interrupted by the page closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
/**
* Defaults to {@code false}. Whether to run the <a
* href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">before unload</a> page handlers.
@@ -3858,13 +3869,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -3900,13 +3915,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -3941,13 +3960,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -6257,7 +6280,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6290,7 +6313,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6321,7 +6344,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6354,7 +6377,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6385,7 +6408,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6418,7 +6441,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6449,7 +6472,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6482,7 +6505,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6513,7 +6536,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6546,7 +6569,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6577,7 +6600,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6610,7 +6633,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -7818,8 +7841,8 @@ public interface Page extends AutoCloseable {
*/
ElementHandle waitForSelector(String selector, WaitForSelectorOptions options);
/**
* The method will block until the codition returns true. All Playwright events will be dispatched while the method is
* waiting for the codition.
* The method will block until the condition returns true. All Playwright events will be dispatched while the method is
* waiting for the condition.
*
* <p> **Usage**
*
@@ -7831,15 +7854,15 @@ public interface Page extends AutoCloseable {
* page.waitForCondition(() -> messages.size() > 3);
* }</pre>
*
* @param condition Codition to wait for.
* @param condition Condition to wait for.
* @since v1.32
*/
default void waitForCondition(BooleanSupplier condition) {
waitForCondition(condition, null);
}
/**
* The method will block until the codition returns true. All Playwright events will be dispatched while the method is
* waiting for the codition.
* The method will block until the condition returns true. All Playwright events will be dispatched while the method is
* waiting for the condition.
*
* <p> **Usage**
*
@@ -7851,7 +7874,7 @@ public interface Page extends AutoCloseable {
* page.waitForCondition(() -> messages.size() > 3);
* }</pre>
*
* @param condition Codition to wait for.
* @param condition Condition to wait for.
* @since v1.32
*/
void waitForCondition(BooleanSupplier condition, WaitForConditionOptions options);
@@ -18,7 +18,7 @@ package com.microsoft.playwright;
/**
* {@code WebError} class represents an unhandled exeception thrown in the page. It is dispatched via the {@link
* {@code WebError} class represents an unhandled exception thrown in the page. It is dispatched via the {@link
* BrowserContext#onWebError BrowserContext.onWebError()} event.
* <pre>{@code
* // Log all uncaught errors to the terminal
@@ -254,11 +254,24 @@ public interface LocatorAssertions {
}
}
class HasAttributeOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public HasAttributeOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
@@ -661,9 +674,22 @@ public interface LocatorAssertions {
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
*
* <p> To check that at least one element from the list is visible, use {@link Locator#first Locator.first()}.
*
* <p> **Usage**
* <pre>{@code
* // A specific element is visible.
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
* ).isVisible();
* }</pre>
*
* @since v1.20
@@ -675,9 +701,22 @@ public interface LocatorAssertions {
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
*
* <p> To check that at least one element from the list is visible, use {@link Locator#first Locator.first()}.
*
* <p> **Usage**
* <pre>{@code
* // A specific element is visible.
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
* ).isVisible();
* }</pre>
*
* @since v1.20
@@ -687,6 +726,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -727,6 +771,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -765,6 +814,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -805,6 +859,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -843,6 +902,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -883,6 +947,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -921,6 +990,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -961,6 +1035,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).containsText("substring");
@@ -1379,6 +1458,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1419,6 +1503,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1457,6 +1546,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1497,6 +1591,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1535,6 +1634,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1575,6 +1679,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1613,6 +1722,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1653,6 +1767,11 @@ public interface LocatorAssertions {
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
* assertThat(page.locator(".title")).hasText("Welcome, Test User");
@@ -1,8 +1,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
@@ -11,6 +10,7 @@ import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.RequestOptions;
import java.io.File;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
@@ -85,11 +85,14 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
byte[] bytes = null;
if (options.data instanceof byte[]) {
bytes = (byte[]) options.data;
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
} else if (options.data instanceof String) {
String stringData = (String) options.data;
if (!isJsonContentType(options.headers) || isJsonParsable(stringData)) {
bytes = (stringData).getBytes(StandardCharsets.UTF_8);
}
}
if (bytes == null) {
params.add("jsonData", gson().toJsonTree(options.data));
params.addProperty("jsonData", gson().toJson(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
@@ -202,4 +205,21 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
return impl;
}
private static boolean isJsonParsable(String value) {
try {
JsonElement result = JsonParser.parseString(value);
if (result != null && result.isJsonPrimitive()) {
JsonPrimitive primitive = result.getAsJsonPrimitive();
if (primitive.isString() && value.equals(primitive.getAsString())) {
// Gson parses unquoted strings too, but we don't want to treat them
// as valid JSON.
return false;
}
}
return true;
} catch (JsonSyntaxException error) {
return false;
}
}
}
@@ -34,9 +34,6 @@ class ArtifactImpl extends ChannelOwner {
public InputStream createReadStream() {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
}
@@ -51,6 +51,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
private String closeReason;
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
@@ -115,6 +117,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
if (browser != null) {
return browser.closeReason;
}
return null;
}
@Override
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -242,8 +254,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
public void close(CloseOptions options) {
withLogging("BrowserContext.close", () -> closeImpl(options));
}
@Override
@@ -251,9 +263,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl() {
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
@@ -275,7 +291,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
artifact.delete();
}
sendMessage("close");
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@@ -594,7 +611,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public R get() {
throw new PlaywrightException("Context closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -672,8 +689,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid);
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (page != null) {
@@ -753,9 +769,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.notify(EventType.CLOSE, this);
}
WritableStream createTempFile(String name) {
WritableStream createTempFile(String name, long lastModifiedMs) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
@@ -42,6 +42,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
BrowserTypeImpl browserType;
BrowserType.LaunchOptions launchOptions;
private Path tracePath;
String closeReason;
enum EventType {
DISCONNECTED,
@@ -67,11 +68,15 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
@Override
public void close() {
withLogging("Browser.close", () -> closeImpl());
public void close(CloseOptions options) {
withLogging("Browser.close", () -> closeImpl(options));
}
private void closeImpl() {
private void closeImpl(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
if (isConnectedOverWebSocket) {
try {
connection.close();
@@ -183,7 +188,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -33,8 +33,6 @@ import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -203,7 +201,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -18,11 +18,11 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -34,6 +34,7 @@ class ChannelOwner extends LoggingSupport {
final String type;
final String guid;
final JsonObject initializer;
private boolean wasCollected;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -57,15 +58,16 @@ class ChannelOwner extends LoggingSupport {
}
}
void disconnect() {
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
parent.objects.remove(guid);
}
connection.unregisterObject(guid);
wasCollected = wasGarbageCollected;
// Dispose all children.
for (ChannelOwner child : new ArrayList<>(objects.values())) {
child.disconnect();
child.disposeChannelOwner(wasGarbageCollected);
}
objects.clear();
}
@@ -91,6 +93,7 @@ class ChannelOwner extends LoggingSupport {
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessageAsync(guid, method, params);
}
@@ -99,9 +102,15 @@ class ChannelOwner extends LoggingSupport {
}
JsonElement sendMessage(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessage(guid, method, params);
}
private void checkNotCollected() {
if (wasCollected)
throw new PlaywrightException("The object has been collected to prevent unbounded heap growth.");
}
<T> T runUntil(Runnable code, Waitable<T> waitable) {
try {
code.run();
@@ -25,7 +25,9 @@ import com.microsoft.playwright.TimeoutError;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -38,6 +40,7 @@ class Message {
JsonObject params;
JsonElement result;
SerializedError error;
JsonArray log;
@Override
public String toString() {
@@ -206,6 +209,30 @@ public class Connection {
dispatch(messageObj);
}
private static String formatCallLog(JsonArray log) {
if (log == null) {
return "";
}
boolean allEmpty = true;
for (JsonElement e: log) {
if (!e.getAsString().isEmpty()) {
allEmpty = false;
break;
}
}
if (allEmpty) {
return "";
}
List<String> lines = new ArrayList<>();
lines.add("");
lines.add("Call log:");
for (JsonElement e: log) {
lines.add("- " + e.getAsString());
}
lines.add("");
return String.join("\n", lines);
}
private void dispatch(Message message) {
// System.out.println("Message: " + message.method + " " + message.id);
if (message.id != 0) {
@@ -218,12 +245,16 @@ public class Connection {
if (message.error == null) {
callback.complete(message.result);
} else {
String callLog = formatCallLog(message.log);
if (message.error.error == null) {
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
callback.completeExceptionally(new PlaywrightException(message.error + callLog));
} else if ("TimeoutError".equals(message.error.error.name)) {
callback.completeExceptionally(new TimeoutError(message.error.error.toString()));
callback.completeExceptionally(new TimeoutError(message.error.error + callLog));
} else if ("TargetClosedError".equals(message.error.error.name)) {
callback.completeExceptionally(new TargetClosedError(message.error.error + callLog));
} else {
callback.completeExceptionally(new DriverException(message.error.error));
callback.completeExceptionally(new DriverException(message.error.error + callLog));
}
}
return;
@@ -252,7 +283,8 @@ public class Connection {
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
boolean wasCollected = message.params.has("reason") && "gc".equals(message.params.get("reason").getAsString());
object.disposeChannelOwner(wasCollected);
return;
}
object.handleEvent(message.method, message.params);
@@ -293,9 +325,6 @@ public class Connection {
case "BrowserContext":
result = new BrowserContextImpl(parent, type, guid, initializer);
break;
case "ConsoleMessage":
result = new ConsoleMessageImpl(parent, type, guid, initializer);
break;
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
@@ -27,17 +27,20 @@ import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
private final JsonObject initializer;
public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
public ConsoleMessageImpl(Connection connection, JsonObject initializer) {
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.initializer = initializer;
}
public String type() {
@@ -22,7 +22,7 @@ import java.io.PrintStream;
import java.io.PrintWriter;
class DriverException extends PlaywrightException {
DriverException(SerializedError.Error error) {
super(error.toString());
DriverException(String error) {
super(error);
}
}
@@ -28,13 +28,12 @@ import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
import static com.microsoft.playwright.impl.Utils.addFilePathUploadParams;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -467,16 +466,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (frame == null) {
throw new Error("Cannot set input files to detached element");
}
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, frame.page().context());
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(Utils.toFilePayloads(files), options);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, frame.page().context());
sendMessage("setInputFiles", params);
}
@Override
@@ -761,17 +761,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFiles", params);
}
@Override
@@ -791,7 +787,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.add("files", toJsonArray(files));
params.add("payloads", toJsonArray(files));
sendMessage("setInputFiles", params);
}
@@ -86,12 +86,14 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, text, options);
}
@Override
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, pattern, options);
}
@@ -105,7 +107,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
}
@Override
@@ -68,6 +68,7 @@ public class PageImpl extends ChannelOwner implements Page {
private final TimeoutSettings timeoutSettings;
private VideoImpl video;
private final PageImpl opener;
private String closeReason;
enum EventType {
CLOSE,
@@ -200,6 +201,13 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.CLOSE, this);
}
private String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
return browserContext.effectiveCloseReason();
}
@Override
public void onClose(Consumer<Page> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -488,6 +496,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
try {
if (ownedContext != null) {
ownedContext.close();
@@ -1341,7 +1350,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -1352,7 +1361,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page crashed");
throw new TargetClosedError("Page crashed");
}
}
@@ -63,7 +63,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
private final LocalUtils localUtils;
private SharedSelectors sharedSelectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -74,10 +73,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
apiRequest = new APIRequestImpl(this);
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
chromium.localUtils = localUtils;
firefox.localUtils = localUtils;
webkit.localUtils = localUtils;
}
void initSharedSelectors(PlaywrightImpl parent) {
@@ -0,0 +1,29 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
public class TargetClosedError extends PlaywrightException {
public TargetClosedError() {
super(null);
}
public TargetClosedError(String message) {
super(message != null ? message : "Target page, context or browser has been closed");
}
}
@@ -77,19 +77,8 @@ class TracingImpl extends ChannelOwner implements Tracing {
connection.localUtils.zip(path, new JsonArray(), stacksId, true, includeSources);
}
@Override
public void start(StartOptions options) {
withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk(StartChunkOptions options) {
withLogging("Tracing.startChunk", () -> {
startChunkImpl(options);
});
}
private void startChunkImpl(StartChunkOptions options) {
if (options == null) {
options = new StartChunkOptions();
}
@@ -116,7 +105,8 @@ class TracingImpl extends ChannelOwner implements Tracing {
stacksId = connection.localUtils().tracingStarted(tracesDir == null ? null : tracesDir.toString(), traceName);
}
private void startImpl(StartOptions options) {
@Override
public void start(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
@@ -131,17 +121,13 @@ class TracingImpl extends ChannelOwner implements Tracing {
@Override
public void stop(StopOptions options) {
withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
sendMessage("tracingStop");
});
stopChunkImpl(options == null ? null : options.path);
sendMessage("tracingStop");
}
@Override
public void stopChunk(StopChunkOptions options) {
withLogging("Tracing.stopChunk", () -> {
stopChunkImpl(options == null ? null : options.path);
});
stopChunkImpl(options == null ? null : options.path);
}
void setTracesDir(Path tracesDir) {
@@ -18,11 +18,9 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.SelectOption;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,6 +30,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.regex.Pattern;
@@ -174,26 +173,21 @@ class Utils {
return mimeType;
}
static final long maxUploadBufferSize = 50 * 1024 * 1024;
static boolean hasLargeFile(Path[] files) {
long totalSize = 0;
for (Path file: files) {
try {
totalSize += Files.size(file);
} catch (IOException e) {
throw new PlaywrightException("Cannot get file size.", e);
}
}
return totalSize > maxUploadBufferSize;
}
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (context.connection.isRemote) {
static void addFilePathUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (files.length == 0) {
// FIXME: shouldBeAbleToResetSelectedFilesWithEmptyFileList tesst hangs in Chromium if we pass empty paths list.
params.add("payloads", new JsonArray());
} else if (context.connection.isRemote) {
List<WritableStream> streams = new ArrayList<>();
JsonArray jsonStreams = new JsonArray();
for (Path path : files) {
WritableStream temp = context.createTempFile(path.getFileName().toString());
long lastModifiedMs;
try {
lastModifiedMs = Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
throw new PlaywrightException("Cannot read file timestamp: " + path, e);
}
WritableStream temp = context.createTempFile(path.getFileName().toString(), lastModifiedMs);
streams.add(temp);
try (OutputStream out = temp.stream()) {
Files.copy(path, out);
@@ -220,7 +214,7 @@ class Utils {
for (FilePayload file: files) {
totalSize += file.buffer.length;
}
if (totalSize > maxUploadBufferSize) {
if (totalSize > 50 * 1024 * 1024) {
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
}
}
@@ -28,6 +28,12 @@ class WritableStream extends ChannelOwner {
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
sendMessage("write", params);
}
@Override
public void close() throws IOException {
super.close();
sendMessage("close");
}
};
}
}
@@ -127,4 +127,15 @@ public class TestBrowser extends TestBase {
session.detach();
}
@Test
void shouldPropagateCloseReasonToPendingActions() {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.waitForPage(() -> {
browser.close(new Browser.CloseOptions().setReason("The reason."));
}));
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
}
}
@@ -38,6 +38,14 @@ public class TestBrowserContextAddCookies extends TestBase {
assertEquals("password=123456", page.evaluate("document.cookie"));
}
// Slightly different rounding on chromium win.
private static List<Cookie> normalizeExpires(List<Cookie> cookies) {
for (Cookie cookie: cookies) {
cookie.expires = (double) Math.round(cookie.expires);
}
return cookies;
}
@Test
void shouldRoundtripCookie() {
page.navigate(server.EMPTY_PAGE);
@@ -53,7 +61,7 @@ public class TestBrowserContextAddCookies extends TestBase {
context.clearCookies();
assertEquals(0, context.cookies().size());
context.addCookies(cookies);
assertJsonEquals(cookies, context.cookies());
assertJsonEquals(normalizeExpires(cookies), normalizeExpires(context.cookies()));
}
@Test
@@ -144,7 +144,7 @@ public class TestBrowserContextBasic extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.waitForPage(() -> context.close());
});
assertTrue(e.getMessage().contains("Context closed"));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
@@ -281,7 +281,16 @@ public class TestBrowserContextBasic extends TestBase {
context.close();
return false;
}));
assertTrue(e.getMessage().contains("Context closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
void shouldPropagateCloseReasonToPendingActions() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForPopup(() -> {
context.close(new BrowserContext.CloseOptions().setReason("The reason."));
}));
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
}
}
@@ -672,7 +672,7 @@ public class TestBrowserContextFetch extends TestBase {
});
page.evaluate("() => setTimeout(closeContext, 1000);");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
e = assertThrows(PlaywrightException.class, () -> context.request().post(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
@@ -175,17 +175,33 @@ public class TestBrowserContextProxy extends TestBase {
// @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00
.setBypass("1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test")));
Page page = context.newPage();
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
assertThrows(PlaywrightException.class, () -> page.navigate("http://1.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://2.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://foo.is.the.another.test/target.html"));
page.navigate("http://3.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
{
Page page = context.newPage();
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://1.non.existent.domain.for.the.test/target.html"));
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://2.non.existent.domain.for.the.test/target.html"));
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://foo.is.the.another.test/target.html"));
page.close();
}
{
Page page = context.newPage();
page.navigate("http://3.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
page.close();
}
context.close();
}
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -39,6 +40,8 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeConnect extends TestBase {
static Path FILE_TO_UPLOAD = Paths.get("src/test/resources/file-to-upload.txt");
private Process browserServer;
private String wsEndpoint;
@@ -349,7 +352,7 @@ public class TestBrowserTypeConnect extends TestBase {
}
browser.close();
}
@Test
void shouldSaveAsVideosFromRemoteBrowser(@TempDir Path tempDir) {
Path videosPath = tempDir.resolve("videosPath");
@@ -501,4 +504,17 @@ public class TestBrowserTypeConnect extends TestBase {
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
@Test
void setInputFilesDhouldPreserveLastModifiedTimestamp() throws IOException {
page.setContent("<input type=file multiple=true/>");
Locator input = page.locator("input");
input.setInputFiles(FILE_TO_UPLOAD);
assertEquals(asList("file-to-upload.txt"), input.evaluate("e => [...e.files].map(f => f.name)"));
List<Double> timestamps = (List<Double>) input.evaluate("e => [...e.files].map(f => f.lastModified)");
FileTime expected = Files.getLastModifiedTime(FILE_TO_UPLOAD);
// On Linux browser sometimes reduces the timestamp by 1ms: 1696272058110.0715 -> 1696272058109 or even
// rounds it to seconds in WebKit: 1696272058110 -> 1696272058000.
assertTrue(Math.abs(timestamps.get(0) - expected.toMillis()) < 1000, "expected: " + expected.toMillis() + "; actual: " + timestamps.get(0));
}
}
@@ -18,6 +18,8 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleConvenience extends TestBase {
@@ -35,6 +37,17 @@ public class TestElementHandleConvenience extends TestBase {
assertEquals("JSHandle@<input checked id=\"check\" foo=\"bar\"\" type=\"checkbox\"/>", check.toString());
}
@Test
void shouldHaveANicePreviewForNonAsciiAttributesChildren() {
page.navigate(server.EMPTY_PAGE);
String text = String.join("", Collections.nCopies(100, "😛"));
page.setContent("<div title='" + text + "'>" + text + "</div>");
ElementHandle handle = page.querySelector("div");
context.waitForCondition(() -> "JSHandle@<div title=\"😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…>😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…</div>"
.equals(handle.toString()), new BrowserContext.WaitForConditionOptions().setTimeout(5_000));
assertEquals("JSHandle@<div title=\"😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…>😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…</div>", handle.toString());
}
@Test
void getAttributeShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
@@ -122,7 +122,7 @@ public class TestEvalOnSelector extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.evalOnSelector("section", "e => e.id");
});
assertTrue(e.getMessage().contains("failed to find element matching selector \"section\""));
assertTrue(e.getMessage().contains("Failed to find element matching selector \"section\""), e.getMessage());
}
@Test
@@ -19,10 +19,14 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
@EnabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="skip")
public class TestFirefoxLauncher extends TestBase {
@Override
@@ -37,7 +41,6 @@ public class TestFirefoxLauncher extends TestBase {
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="skip")
void shouldPassFirefoxUserPreferences() {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().setFirefoxUserPrefs(
mapOf(
@@ -49,4 +52,19 @@ public class TestFirefoxLauncher extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate("http://example.com"));
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
@Test
void shouldPassFirefoxUserPreferencesInPersistent(@TempDir Path tmpDir) {
BrowserType.LaunchPersistentContextOptions options = new BrowserType.LaunchPersistentContextOptions().setFirefoxUserPrefs(
mapOf(
"network.proxy.type", 1,
"network.proxy.http", "127.0.0.1",
"network.proxy.http_port", 3333));
initBrowserType();
context = browserType.launchPersistentContext(tmpDir.resolve("user-data-dir"), options);
assertNotNull(context);
Page page = context.pages().get(0);
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate("http://example.com"));
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
}
@@ -246,6 +246,14 @@ public class TestLocatorAssertions extends TestBase {
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
@Test
void hasAttributeTextIgnoreCase() {
page.setContent("<div id=NoDe>Text content</div>");
Locator locator = page.locator("#NoDe");
assertThat(locator).hasAttribute("id", "node", new LocatorAssertions.HasAttributeOptions().setIgnoreCase(true));
assertThat(locator).not().hasAttribute("id", "node");
}
@Test
void hasAttributeTextPass() {
page.setContent("<div id=node>Text content</div>");
@@ -112,12 +112,12 @@ public class TestPageBasic extends TestBase {
PlaywrightException e2 = assertThrows(PlaywrightException.class, () -> {
newPage.waitForRequest(server.EMPTY_PAGE, () -> newPage.close());
});
assertTrue(e2.getMessage().contains("Page closed"));
assertFalse(e2.getMessage().contains("Timeout"));
assertTrue(e2.getMessage().contains("Target page, context or browser has been closed"), e2.getMessage());
assertFalse(e2.getMessage().contains("Timeout"), e2.getMessage());
});
});
assertTrue(e1.getMessage().contains("Page closed"));
assertFalse(e1.getMessage().contains("Timeout"));
assertTrue(e1.getMessage().contains("Target page, context or browser has been closed"), e1.getMessage());
assertFalse(e1.getMessage().contains("Timeout"), e1.getMessage());
}
@Test
@@ -336,6 +336,15 @@ public class TestPageBasic extends TestBase {
page.close();
return false;
}));
assertTrue(e.getMessage().contains("Page closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
void shouldPropagateCloseReasonToPendingActions() {
Page page = context.newPage();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForPopup(() -> {
page.close(new Page.CloseOptions().setReason("The reason."));
}));
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
}
}
@@ -391,7 +391,7 @@ public class TestPageEvaluate extends TestBase {
assertNotNull(element);
element.dispose();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.evaluate("e => e.textContent", element));
assertTrue(e.getMessage().contains("JSHandle is disposed"));
assertTrue(e.getMessage().contains("no object with guid"), e.getMessage());
}
@Test
@@ -379,11 +379,7 @@ public class TestPageKeyboard extends TestBase {
} else {
assertEquals("Meta", eventData.get("key"));
}
if (isFirefox()) {
assertEquals("OSLeft", eventData.get("code"));
} else {
assertEquals("MetaLeft", eventData.get("code"));
}
assertEquals("MetaLeft", eventData.get("code"));
if (isFirefox() && !isMac) {
assertFalse((Boolean) eventData.get("metaKey"), eventData.toString());
} else {
@@ -135,4 +135,14 @@ public class TestPageNetworkRequest extends TestBase {
assertTrue(error[0].getMessage().contains("Frame for this navigation request is not available"), error[0].getMessage());
}
@Test
void shouldThrowIfRequestWasGCed() {
List<Request> requests = new ArrayList<>();
page.onRequest(req -> requests.add(req));
for (int i = 0; i < 1001; i++) {
page.navigate(server.EMPTY_PAGE);
}
PlaywrightException e = assertThrows(PlaywrightException.class, () -> requests.get(0).response());
assertEquals("The object has been collected to prevent unbounded heap growth.", e.getMessage());
}
}
@@ -389,7 +389,7 @@ public class TestPageRoute extends TestBase {
Request r = intercepted.get(1);
for (String url : asList("/one-style.css", "/two-style.css", "/three-style.css", "/four-style.css")) {
assertEquals("stylesheet", r.resourceType());
assertTrue(r.url().contains(url));
assertTrue(r.url().contains(url), "actual: " + r.url() + "; expected: " + url);
r = r.redirectedTo();
}
assertNull(r);
@@ -21,11 +21,13 @@ import com.microsoft.playwright.options.FilePayload;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.commons.support.AnnotationSupport;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@@ -34,8 +36,10 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static com.microsoft.playwright.Utils.relativePathOrSkipTest;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
@@ -145,8 +149,7 @@ public class TestPageSetInputFiles extends TestBase {
" return events;\n" +
" }");
Path cwd = Paths.get("").toAbsolutePath();
Path relativeUploadPath = cwd.relativize(uploadFile);
Path relativeUploadPath = relativePathOrSkipTest(uploadFile);
assertFalse(relativeUploadPath.isAbsolute());
input.setInputFiles(relativeUploadPath);
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
@@ -402,5 +405,18 @@ public class TestPageSetInputFiles extends TestBase {
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
assertTrue(fileChooser.isMultiple());
}
@Test
void shouldPreserveLastModifiedTimestamp() throws IOException {
page.setContent("<input type=file multiple=true/>");
Locator input = page.locator("input");
input.setInputFiles(FILE_TO_UPLOAD);
assertEquals(asList("file-to-upload.txt"), input.evaluate("e => [...e.files].map(f => f.name)"));
List<Double> timestamps = (List<Double>) input.evaluate("e => [...e.files].map(f => f.lastModified)");
FileTime expected = Files.getLastModifiedTime(FILE_TO_UPLOAD);
// On Linux browser sometimes reduces the timestamp by 1ms: 1696272058110.0715 -> 1696272058109 or even
// rounds it to seconds in WebKit: 1696272058110 -> 1696272058000.
assertTrue(Math.abs(timestamps.get(0) - expected.toMillis()) < 1000, "expected: " + expected.toMillis() + "; actual: " + timestamps.get(0));
}
}
@@ -0,0 +1,24 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static com.microsoft.playwright.Utils.relativePathOrSkipTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestVideo extends TestBase {
@Test
void shouldWorkWithRelativePathForRecordVideoDir(@TempDir Path tmpDir) {
Path relativeDir = relativePathOrSkipTest(tmpDir);
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordVideoSize(320, 240).setRecordVideoDir(relativeDir));
Page page = context.newPage();
Path videoPath = page.video().path();
context.close();
assertTrue(videoPath.isAbsolute(), "videosPath = " + videoPath);
assertTrue(Files.exists(videoPath), "videosPath = " + videoPath);
}
}
@@ -199,7 +199,7 @@ public class TestWebSocket extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
ws.waitForFrameSent(() -> page.close());
});
assertTrue(e.getMessage().contains("Page closed"));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
@@ -57,7 +57,7 @@ public class TestWorkers extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
workerThisObj.getProperty("self");
});
assertTrue(e.getMessage().contains("Target closed") || e.getMessage().contains("Worker was closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed") || e.getMessage().contains("Worker was closed"), e.getMessage());
}
@@ -19,11 +19,13 @@ package com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.Assumptions;
import java.io.*;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -214,4 +216,17 @@ class Utils {
static String generateDifferentOriginPort(final Server server){
return server.PREFIX.replace(String.valueOf(server.PORT), String.valueOf(server.PORT+1));
}
static Path relativePathOrSkipTest(Path path) {
Path cwd = Paths.get("").toAbsolutePath();
try {
return cwd.relativize(path.toAbsolutePath());
} catch (IllegalArgumentException e) {
// May happen on Windows when the path and temp are on different disks.
if (e.getMessage().contains("has different root")) {
Assumptions.assumeTrue(false, "cwd is on another disk, skipping the test.");
}
throw e;
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.38.0-SNAPSHOT</version>
<version>1.40.0</version>
<packaging>pom</packaging>
<name>Playwright Parent Project</name>
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
+1 -1
View File
@@ -1 +1 @@
1.38.0
1.40.0
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>api-generator</artifactId>
<version>1.38.0-SNAPSHOT</version>
<version>1.40.0</version>
<name>Playwright - API Generator</name>
<description>
This is an internal module used to generate Java API from the upstream Playwright
+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.38.0-SNAPSHOT</version>
<version>1.40.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.38.0-SNAPSHOT</version>
<version>1.40.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.38.0-SNAPSHOT</version>
<version>1.40.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.38.0-SNAPSHOT</version>
<version>1.40.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.38.0-SNAPSHOT</version>
<version>1.40.0</version>
<name>Playwright - Update Version in Documentation</name>
<description>
This is an internal module used to update versions in the documentation based on
+1 -1
View File
@@ -24,7 +24,7 @@ RUN apt-get update && \
# Create the pwuser
adduser pwuser
RUN VERSION=3.8.8 && \
RUN VERSION=3.9.4 && \
curl -o - https://archive.apache.org/dist/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz | tar zxfv - -C /opt/ && \
ln -s /opt/apache-maven-$VERSION/bin/mvn /usr/local/bin/
+10 -1
View File
@@ -24,7 +24,7 @@ RUN apt-get update && \
# Create the pwuser
adduser pwuser
RUN VERSION=3.8.8 && \
RUN VERSION=3.9.4 && \
curl -o - https://archive.apache.org/dist/maven/maven-3/$VERSION/binaries/apache-maven-$VERSION-bin.tar.gz | tar zxfv - -C /opt/ && \
ln -s /opt/apache-maven-$VERSION/bin/mvn /usr/local/bin/
@@ -54,4 +54,13 @@ RUN cd /tmp/pw-java && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
rm -rf /tmp/pw-java && \
# Workaround for https://github.com/microsoft/playwright/issues/27313
# While the gstreamer plugin load process can be in-process, it ended up throwing
# an error that it can't have libsoup2 and libsoup3 in the same process because
# libgstwebrtc is linked against libsoup2. So we just remove the plugin.
if [ "$(uname -m)" = "aarch64" ]; then \
rm /usr/lib/aarch64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \
else \
rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \
fi && \
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH