From 918084b04ad3d2cf4f8369688cc8e4d0bb43258f Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 6 Mar 2025 11:33:04 -0800 Subject: [PATCH 1/5] chore: roll 1.50.0 driver, implement new features Fixes: https://github.com/microsoft/playwright-java/issues/1731 --- README.md | 6 +- examples/pom.xml | 2 +- .../com/microsoft/playwright/APIRequest.java | 11 + .../playwright/APIRequestContext.java | 11 + .../com/microsoft/playwright/Browser.java | 30 ++ .../microsoft/playwright/BrowserContext.java | 23 +- .../com/microsoft/playwright/BrowserType.java | 43 ++- .../microsoft/playwright/ElementHandle.java | 8 +- .../com/microsoft/playwright/Locator.java | 39 +- .../java/com/microsoft/playwright/Page.java | 21 +- .../com/microsoft/playwright/Touchscreen.java | 3 + .../com/microsoft/playwright/WebSocket.java | 5 +- .../playwright/assertions/PageAssertions.java | 4 +- .../playwright/impl/APIResponseImpl.java | 28 +- .../playwright/impl/BrowserContextImpl.java | 24 +- .../playwright/impl/BrowserTypeImpl.java | 5 + .../playwright/impl/FrameLocatorImpl.java | 2 +- .../playwright/impl/LocatorImpl.java | 16 +- .../playwright/impl/Serialization.java | 2 + .../playwright/options/Contrast.java | 22 ++ .../playwright/TestBrowserContextFetch.java | 7 +- .../TestBrowserContextStorageState.java | 94 +++++ .../TestDefaultBrowserContext2.java | 22 +- .../microsoft/playwright/TestGlobalFetch.java | 32 ++ .../microsoft/playwright/TestLocatorMisc.java | 17 + .../playwright/TestPageEmulateMedia.java | 14 + .../resources/to-do-notifications/LICENSE | 116 ++++++ .../resources/to-do-notifications/README.md | 1 + .../resources/to-do-notifications/index.html | 108 ++++++ .../to-do-notifications/manifest.webapp | 18 + .../to-do-notifications/scripts/todo.js | 354 ++++++++++++++++++ .../to-do-notifications/style/style.css | 248 ++++++++++++ scripts/DRIVER_VERSION | 2 +- 33 files changed, 1264 insertions(+), 74 deletions(-) create mode 100644 playwright/src/main/java/com/microsoft/playwright/options/Contrast.java create mode 100644 playwright/src/test/resources/to-do-notifications/LICENSE create mode 100644 playwright/src/test/resources/to-do-notifications/README.md create mode 100644 playwright/src/test/resources/to-do-notifications/index.html create mode 100644 playwright/src/test/resources/to-do-notifications/manifest.webapp create mode 100644 playwright/src/test/resources/to-do-notifications/scripts/todo.js create mode 100644 playwright/src/test/resources/to-do-notifications/style/style.css diff --git a/README.md b/README.md index 812f25ba1..e81930130 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 133.0.6943.16 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| WebKit 18.2 | ✅ | ✅ | ✅ | -| Firefox 134.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 134.0.6998.35 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| WebKit 18.4 | ✅ | ✅ | ✅ | +| Firefox 135.0 | :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/intro#system-requirements) for details. diff --git a/examples/pom.xml b/examples/pom.xml index 7dfb7dc6f..86f330fec 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -10,7 +10,7 @@ Playwright Client Examples UTF-8 - 1.49.0 + 1.51.0 diff --git a/playwright/src/main/java/com/microsoft/playwright/APIRequest.java b/playwright/src/main/java/com/microsoft/playwright/APIRequest.java index 48b522640..85833f084 100644 --- a/playwright/src/main/java/com/microsoft/playwright/APIRequest.java +++ b/playwright/src/main/java/com/microsoft/playwright/APIRequest.java @@ -59,6 +59,10 @@ class NewContextOptions { * An object containing additional HTTP headers to be sent with every request. Defaults to none. */ public Map extraHTTPHeaders; + /** + * Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes. + */ + public Boolean failOnStatusCode; /** * Credentials for HTTP authentication. If * no origin is specified, the username and password are sent to any servers upon unauthorized responses. @@ -138,6 +142,13 @@ public NewContextOptions setExtraHTTPHeaders(Map extraHTTPHeader this.extraHTTPHeaders = extraHTTPHeaders; return this; } + /** + * Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes. + */ + public NewContextOptions setFailOnStatusCode(boolean failOnStatusCode) { + this.failOnStatusCode = failOnStatusCode; + return this; + } /** * Credentials for HTTP authentication. If * no origin is specified, the username and password are sent to any servers upon unauthorized responses. diff --git a/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java b/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java index a2f0d7432..34b25000d 100644 --- a/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/APIRequestContext.java @@ -58,12 +58,23 @@ public DisposeOptions setReason(String reason) { } } class StorageStateOptions { + /** + * Set to {@code true} to include IndexedDB in the storage state snapshot. + */ + public Boolean indexedDB; /** * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. */ public Path path; + /** + * Set to {@code true} to include IndexedDB in the storage state snapshot. + */ + public StorageStateOptions setIndexedDB(boolean indexedDB) { + this.indexedDB = indexedDB; + return this; + } /** * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index 3b32211ed..265e02fdb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -118,6 +118,12 @@ class NewContextOptions { * "light"}. */ public Optional colorScheme; + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public Optional contrast; /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. @@ -335,6 +341,15 @@ public NewContextOptions setColorScheme(ColorScheme colorScheme) { this.colorScheme = Optional.ofNullable(colorScheme); return this; } + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public NewContextOptions setContrast(Contrast contrast) { + this.contrast = Optional.ofNullable(contrast); + return this; + } /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. @@ -671,6 +686,12 @@ class NewPageOptions { * "light"}. */ public Optional colorScheme; + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public Optional contrast; /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. @@ -888,6 +909,15 @@ public NewPageOptions setColorScheme(ColorScheme colorScheme) { this.colorScheme = Optional.ofNullable(colorScheme); return this; } + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public NewPageOptions setContrast(Contrast contrast) { + this.contrast = Optional.ofNullable(contrast); + return this; + } /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index d0ef27190..be6b18654 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -407,12 +407,31 @@ public RouteFromHAROptions setUrl(Pattern url) { } } class StorageStateOptions { + /** + * Set to {@code true} to include IndexedDB in + * the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase + * Authentication, enable this. + * + *

NOTE: IndexedDBs with typed arrays are currently not supported. + */ + public Boolean indexedDB; /** * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. */ public Path path; + /** + * Set to {@code true} to include IndexedDB in + * the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase + * Authentication, enable this. + * + *

NOTE: IndexedDBs with typed arrays are currently not supported. + */ + public StorageStateOptions setIndexedDB(boolean indexedDB) { + this.indexedDB = indexedDB; + return this; + } /** * The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current * working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. @@ -1435,7 +1454,7 @@ default void routeFromHAR(Path har) { */ void setOffline(boolean offline); /** - * Returns storage state for this browser context, contains current cookies and local storage snapshot. + * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot. * * @since v1.8 */ @@ -1443,7 +1462,7 @@ default String storageState() { return storageState(null); } /** - * Returns storage state for this browser context, contains current cookies and local storage snapshot. + * Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot. * * @since v1.8 */ diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index 63dc1284c..b100387de 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -497,6 +497,12 @@ class LaunchPersistentContextOptions { * "light"}. */ public Optional colorScheme; + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public Optional contrast; /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. @@ -816,6 +822,15 @@ public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) { this.colorScheme = Optional.ofNullable(colorScheme); return this; } + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See + * {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets + * emulation to system defaults. Defaults to {@code "no-preference"}. + */ + public LaunchPersistentContextOptions setContrast(Contrast contrast) { + this.contrast = Optional.ofNullable(contrast); + return this; + } /** * Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about emulating devices with device scale factor. @@ -1197,22 +1212,24 @@ public LaunchPersistentContextOptions setViewportSize(ViewportSize viewportSize) } } /** - * This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code - * BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is - * compatible with 1.2.x). + * This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js. + * + *

NOTE: The major and minor version of the Playwright instance that connects needs to match the version of Playwright that + * launches the browser (1.2.3 → is compatible with 1.2.x). * - * @param wsEndpoint A browser websocket endpoint to connect to. + * @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}. * @since v1.8 */ default Browser connect(String wsEndpoint) { return connect(wsEndpoint, null); } /** - * This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code - * BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is - * compatible with 1.2.x). + * This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js. * - * @param wsEndpoint A browser websocket endpoint to connect to. + *

NOTE: The major and minor version of the Playwright instance that connects needs to match the version of Playwright that + * launches the browser (1.2.3 → is compatible with 1.2.x). + * + * @param wsEndpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}. * @since v1.8 */ Browser connect(String wsEndpoint, ConnectOptions options); @@ -1223,6 +1240,11 @@ default Browser connect(String wsEndpoint) { * *

NOTE: Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. * + *

NOTE: This connection is significantly lower fidelity than the Playwright protocol connection via {@link + * com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use + * advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect + * BrowserType.connect()}. + * *

Usage *

{@code
    * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1244,6 +1266,11 @@ default Browser connectOverCDP(String endpointURL) {
    *
    * 

NOTE: Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers. * + *

NOTE: This connection is significantly lower fidelity than the Playwright protocol connection via {@link + * com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use + * advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect + * BrowserType.connect()}. + * *

Usage *

{@code
    * Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
index 59b92da37..cc7720101 100644
--- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
+++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java
@@ -599,7 +599,9 @@ class ScreenshotOptions {
     public ScreenshotCaret caret;
     /**
      * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
-     * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
+     * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
+     * invisible elements, see Matching only
+     * visible elements to disable that.
      */
     public List mask;
     /**
@@ -673,7 +675,9 @@ public ScreenshotOptions setCaret(ScreenshotCaret caret) {
     }
     /**
      * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
-     * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
+     * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
+     * invisible elements, see Matching only
+     * visible elements to disable that.
      */
     public ScreenshotOptions setMask(List mask) {
       this.mask = mask;
diff --git a/playwright/src/main/java/com/microsoft/playwright/Locator.java b/playwright/src/main/java/com/microsoft/playwright/Locator.java
index 7173b0432..1fdaea343 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Locator.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Locator.java
@@ -715,6 +715,10 @@ class FilterOptions {
      * 
Playwright
}. */ public Object hasText; + /** + * Only matches visible or invisible elements. + */ + public Boolean visible; /** * Narrows down the results of the method to those which contain elements matching this relative locator. For example, @@ -777,6 +781,13 @@ public FilterOptions setHasText(Pattern hasText) { this.hasText = hasText; return this; } + /** + * Only matches visible or invisible elements. + */ + public FilterOptions setVisible(boolean visible) { + this.visible = visible; + return this; + } } class FocusOptions { /** @@ -1519,7 +1530,9 @@ class ScreenshotOptions { public ScreenshotCaret caret; /** * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box - * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. + * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to + * invisible elements, see Matching only + * visible elements to disable that. */ public List mask; /** @@ -1593,7 +1606,9 @@ public ScreenshotOptions setCaret(ScreenshotCaret caret) { } /** * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box - * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. + * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to + * invisible elements, see Matching only + * visible elements to disable that. */ public ScreenshotOptions setMask(List mask) { this.mask = mask; @@ -2840,10 +2855,6 @@ default ElementHandle elementHandle() { *

If {@code expression} throws or rejects, this method throws. * *

Usage - *

{@code
-   * Locator tweets = page.locator(".tweet .retweets");
-   * assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
-   * }
* * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * automatically invoked. @@ -2868,10 +2879,6 @@ default Object evaluate(String expression, Object arg) { *

If {@code expression} throws or rejects, this method throws. * *

Usage - *

{@code
-   * Locator tweets = page.locator(".tweet .retweets");
-   * assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
-   * }
* * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * automatically invoked. @@ -2895,10 +2902,6 @@ default Object evaluate(String expression) { *

If {@code expression} throws or rejects, this method throws. * *

Usage - *

{@code
-   * Locator tweets = page.locator(".tweet .retweets");
-   * assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
-   * }
* * @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is * automatically invoked. @@ -5212,7 +5215,9 @@ default void setInputFiles(FilePayload[] files) { */ void setInputFiles(FilePayload[] files, SetInputFilesOptions options); /** - * Perform a tap gesture on the element matching the locator. + * Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually + * dispatching touch events, see the emulating legacy touch + * events page. * *

Details * @@ -5238,7 +5243,9 @@ default void tap() { tap(null); } /** - * Perform a tap gesture on the element matching the locator. + * Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually + * dispatching touch events, see the emulating legacy touch + * events page. * *

Details * diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index d5d111532..4a3cd7314 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -965,6 +965,11 @@ class EmulateMediaOptions { * {@code "no-preference"} is deprecated. */ public Optional colorScheme; + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing + * {@code null} disables contrast emulation. + */ + public Optional contrast; /** * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code * null} disables forced colors emulation. @@ -991,6 +996,14 @@ public EmulateMediaOptions setColorScheme(ColorScheme colorScheme) { this.colorScheme = Optional.ofNullable(colorScheme); return this; } + /** + * Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. Passing + * {@code null} disables contrast emulation. + */ + public EmulateMediaOptions setContrast(Contrast contrast) { + this.contrast = Optional.ofNullable(contrast); + return this; + } /** * Emulates {@code "forced-colors"} media feature, supported values are {@code "active"} and {@code "none"}. Passing {@code * null} disables forced colors emulation. @@ -2522,7 +2535,9 @@ class ScreenshotOptions { public Boolean fullPage; /** * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box - * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. + * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to + * invisible elements, see Matching only + * visible elements to disable that. */ public List mask; /** @@ -2617,7 +2632,9 @@ public ScreenshotOptions setFullPage(boolean fullPage) { } /** * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box - * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. + * {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to + * invisible elements, see Matching only + * visible elements to disable that. */ public ScreenshotOptions setMask(List mask) { this.mask = mask; diff --git a/playwright/src/main/java/com/microsoft/playwright/Touchscreen.java b/playwright/src/main/java/com/microsoft/playwright/Touchscreen.java index f59a32e01..2024016c1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Touchscreen.java +++ b/playwright/src/main/java/com/microsoft/playwright/Touchscreen.java @@ -20,6 +20,9 @@ /** * The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the * touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true. + * + *

This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch + * events, see the emulating legacy touch events page. */ public interface Touchscreen { /** diff --git a/playwright/src/main/java/com/microsoft/playwright/WebSocket.java b/playwright/src/main/java/com/microsoft/playwright/WebSocket.java index 7a60b1923..ab777879c 100644 --- a/playwright/src/main/java/com/microsoft/playwright/WebSocket.java +++ b/playwright/src/main/java/com/microsoft/playwright/WebSocket.java @@ -20,7 +20,10 @@ import java.util.function.Predicate; /** - * The {@code WebSocket} class represents websocket connections in the page. + * The {@code WebSocket} class represents WebSocket connections within a page. It provides the ability to inspect and + * manipulate the data being transmitted and received. + * + *

If you want to intercept or modify WebSocket frames, consider using {@code WebSocketRoute}. */ public interface WebSocket { diff --git a/playwright/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java b/playwright/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java index 2bfbfa447..734c9a1f9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java +++ b/playwright/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java @@ -54,7 +54,7 @@ public HasTitleOptions setTimeout(double timeout) { class HasURLOptions { /** * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular - * expression flag if specified. + * expression parameter if specified. A provided predicate ignores this flag. */ public Boolean ignoreCase; /** @@ -64,7 +64,7 @@ class HasURLOptions { /** * Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular - * expression flag if specified. + * expression parameter if specified. A provided predicate ignores this flag. */ public HasURLOptions setIgnoreCase(boolean ignoreCase) { this.ignoreCase = ignoreCase; diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java index e80fd3d35..98d6dbbe6 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/APIResponseImpl.java @@ -46,22 +46,20 @@ class APIResponseImpl implements APIResponse { @Override public byte[] body() { - return context.withLogging("APIResponse.body", () -> { - try { - JsonObject params = new JsonObject(); - params.addProperty("fetchUid", fetchUid()); - JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject(); - if (!json.has("binary")) { - throw new PlaywrightException("Response has been disposed"); - } - return Base64.getDecoder().decode(json.get("binary").getAsString()); - } catch (PlaywrightException e) { - if (isSafeCloseError(e)) { - throw new PlaywrightException("Response has been disposed"); - } - throw e; + try { + JsonObject params = new JsonObject(); + params.addProperty("fetchUid", fetchUid()); + JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject(); + if (!json.has("binary")) { + throw new PlaywrightException("Response has been disposed"); } - }); + return Base64.getDecoder().decode(json.get("binary").getAsString()); + } catch (PlaywrightException e) { + if (isSafeCloseError(e)) { + throw new PlaywrightException("Response has been disposed"); + } + throw e; + } } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 5cf39fe9d..c1bb3dd72 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -623,14 +623,22 @@ public void setOffline(boolean offline) { @Override public String storageState(StorageStateOptions options) { - return withLogging("BrowserContext.storageState", () -> { - JsonElement json = sendMessage("storageState"); - String storageState = json.toString(); - if (options != null && options.path != null) { - Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path); - } - return storageState; - }); + return withLogging("BrowserContext.storageState", () -> storageStateImpl(options)); + } + + private String storageStateImpl(StorageStateOptions options) { + if (options == null) { + options = new StorageStateOptions(); + } + JsonObject params = gson().toJsonTree(options).getAsJsonObject(); + params.remove("path"); + JsonElement json = sendMessage("storageState", params); + + String storageState = json.toString(); + if (options.path != null) { + Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path); + } + return storageState; } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java index 23a5dcada..9521f77a7 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.function.Consumer; import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter; @@ -196,6 +197,10 @@ private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchP } JsonObject params = gson().toJsonTree(options).getAsJsonObject(); + if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) { + Path cwd = Paths.get("").toAbsolutePath(); + userDataDir = cwd.resolve(userDataDir); + } params.addProperty("userDataDir", userDataDir.toString()); if (recordHar != null) { params.add("recordHar", recordHar); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java index 648704133..ffc11bcd1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameLocatorImpl.java @@ -136,6 +136,6 @@ public FrameLocator nth(int index) { @Override public Locator owner() { - return new LocatorImpl(frame, frameSelector); + return new LocatorImpl(frame, frameSelector, null); } } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java index 9bb3eb197..ea66f9ec2 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java @@ -21,29 +21,25 @@ import com.microsoft.playwright.*; import com.microsoft.playwright.options.*; -import java.lang.reflect.Field; import java.nio.file.Path; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.function.BiFunction; import java.util.regex.Pattern; import static com.microsoft.playwright.impl.LocatorUtils.*; import static com.microsoft.playwright.impl.Serialization.gson; import static com.microsoft.playwright.impl.Utils.convertType; -import static com.microsoft.playwright.impl.Utils.toJsRegexFlags; class LocatorImpl implements Locator { final FrameImpl frame; final String selector; - LocatorImpl(FrameImpl frame, String frameSelector) { - this(frame, frameSelector, null); + LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) { + this(frame, selector, options, null); } - public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) { + private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Boolean visible) { this.frame = frame; if (options != null) { if (options.hasText != null) { @@ -65,6 +61,9 @@ public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) { selector += " >> internal:has-not=" + gson().toJson(locator.selector); } } + if (visible != null) { + selector += " >> visible=" + visible; + } this.selector = selector; } @@ -252,7 +251,8 @@ public void fill(String value, FillOptions options) { @Override public Locator filter(FilterOptions options) { - return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class)); + Boolean visible = (options == null) ? null : options.visible; + return new LocatorImpl(frame, selector, convertType(options, LocatorOptions.class), visible); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index f0384ec98..633e75761 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -47,6 +47,7 @@ class Serialization { .registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe()) .registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer()) .registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer()) + .registerTypeAdapter(Contrast.class, new ToLowerCaseAndDashSerializer()) .registerTypeAdapter(Media.class, new ToLowerCaseSerializer()) .registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer()) .registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer()) @@ -425,6 +426,7 @@ private static class OptionalSerializer implements JsonSerializer> { private static boolean isSupported(Type type) { return new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || + new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()); diff --git a/playwright/src/main/java/com/microsoft/playwright/options/Contrast.java b/playwright/src/main/java/com/microsoft/playwright/options/Contrast.java new file mode 100644 index 000000000..d5be88db4 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/options/Contrast.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright.options; + +public enum Contrast { + NO_PREFERENCE, + MORE +} \ No newline at end of file diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java index 0e9e9acc7..f8592f82e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java @@ -22,8 +22,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import java.io.*; -import java.nio.charset.Charset; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.text.ParseException; @@ -34,7 +35,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import static com.microsoft.playwright.Utils.*; +import static com.microsoft.playwright.Utils.mapOf; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java index 44fcafd9d..fba305d6e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java @@ -29,6 +29,7 @@ import static com.microsoft.playwright.Utils.assertJsonEquals; import static com.microsoft.playwright.Utils.mapOf; +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; public class TestBrowserContextStorageState extends TestBase { @@ -169,4 +170,97 @@ void shouldSerialiseStorageStateWithLoneSurrogates() { " }]\n" + "}]}", new Gson().fromJson(storageState, JsonObject.class)); } + + @Test + void shouldSupportIndexedDB() { + page.navigate(server.PREFIX + "/to-do-notifications/index.html"); + page.locator("label:has-text('Task title')").fill("Pet the cat"); + page.locator("label:has-text('Hours')").fill("1"); + page.locator("label:has-text('Mins')").fill("1"); + page.locator("text=Add Task").click(); + + String storageState = page.context().storageState(new BrowserContext.StorageStateOptions().setIndexedDB(true)); + assertJsonEquals("{\"cookies\":[],\"origins\":[\n" + + " {\n" + + " \"origin\": \"" + server.PREFIX + "\",\n" + + " \"localStorage\": [],\n" + + " \"indexedDB\": [\n" + + " {\n" + + " \"name\": \"toDoList\",\n" + + " \"version\": 4,\n" + + " \"stores\": [\n" + + " {\n" + + " \"name\": \"toDoList\",\n" + + " \"autoIncrement\": false,\n" + + " \"keyPath\": \"taskTitle\",\n" + + " \"records\": [\n" + + " {\n" + + " \"value\": {\n" + + " \"day\": \"01\",\n" + + " \"hours\": \"1\",\n" + + " \"minutes\": \"1\",\n" + + " \"month\": \"January\",\n" + + " \"notified\": \"no\",\n" + + " \"taskTitle\": \"Pet the cat\",\n" + + " \"year\": \"2025\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"indexes\": [\n" + + " {\n" + + " \"name\": \"day\",\n" + + " \"keyPath\": \"day\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"hours\",\n" + + " \"keyPath\": \"hours\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"minutes\",\n" + + " \"keyPath\": \"minutes\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"month\",\n" + + " \"keyPath\": \"month\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"notified\",\n" + + " \"keyPath\": \"notified\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"year\",\n" + + " \"keyPath\": \"year\",\n" + + " \"multiEntry\": false,\n" + + " \"unique\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "]}", new Gson().fromJson(storageState, JsonObject.class)); + + BrowserContext context = browser.newContext(new Browser.NewContextOptions().setStorageState(storageState)); + assertEquals(storageState, context.storageState(new BrowserContext.StorageStateOptions().setIndexedDB(true))); + + Page recreatedPage = context.newPage(); + recreatedPage.navigate(server.PREFIX + "/to-do-notifications/index.html"); + assertThat(recreatedPage.locator("#task-list")).matchesAriaSnapshot("\n" + + " - list:\n" + + " - listitem:\n" + + " - text: /Pet the cat/\n"); + assertEquals("{\"cookies\":[],\"origins\":[]}", context.storageState( + new BrowserContext.StorageStateOptions().setIndexedDB(false))); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java index fd69326d6..6716365dc 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import com.microsoft.playwright.options.Contrast; import com.microsoft.playwright.options.Geolocation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; @@ -28,6 +29,7 @@ import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -282,4 +284,22 @@ void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionEx assertEquals(1, fields.size()); assertEquals("200MB.zip", fields.get(0).filename); assertEquals(200 * 1024 * 1024, fields.get(0).content.length()); - }} + } + + @Test + void shouldSupportContrastOption() { + Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().setContrast(Contrast.MORE)); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-contrast: more)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-contrast: no-preference)').matches")); + } + + @Test + void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception { + Path userDataDir = tempDir.resolve("user-data-dir"); + Path cwd = Paths.get("").toAbsolutePath(); + Path relativePath = cwd.relativize(userDataDir); + BrowserContext context = browserType.launchPersistentContext(relativePath); + assertTrue(Files.list(userDataDir).count() > 0); + context.close(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestGlobalFetch.java b/playwright/src/test/java/com/microsoft/playwright/TestGlobalFetch.java index e31283861..1993d2aa5 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestGlobalFetch.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestGlobalFetch.java @@ -548,4 +548,36 @@ public void shouldRetryECONNRESET() { assertEquals(4, requestCount[0]); requestContext.dispose(); } + + @Test + public void shouldThrowWhenFailOnStatusCodeIsSetToTrue() { + APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setFailOnStatusCode(true)); + server.setRoute("/empty.html", exchange -> { + exchange.getResponseHeaders().set("Content-Length", "10"); + exchange.getResponseHeaders().set("Content-type", "text/plain"); + exchange.sendResponseHeaders(404, 10); + try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Not found."); + } + }); + PlaywrightException error = assertThrows(PlaywrightException.class, () -> { + request.get(server.EMPTY_PAGE); + }); + assertTrue(error.getMessage().contains("404 Not Found")); + } + + @Test + public void shouldNotThrowWhenFailOnStatusCodeIsSetToFalse() { + APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setFailOnStatusCode(false)); + server.setRoute("/empty.html", exchange -> { + exchange.getResponseHeaders().set("Content-Length", "10"); + exchange.getResponseHeaders().set("Content-type", "text/plain"); + exchange.sendResponseHeaders(404, 10); + try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) { + writer.write("Not found."); + } + }); + APIResponse response = request.get(server.EMPTY_PAGE); + assertEquals(404, response.status()); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java index 9f3aea327..5e057ffdc 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestLocatorMisc.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.*; public class TestLocatorMisc extends TestBase{ @@ -123,4 +124,20 @@ void shouldPressSequentially() { page.locator("input").pressSequentially("hello"); assertEquals("hello", page.evalOnSelector("input", "input => input.value")); } + + @Test + public void shouldSupportFilterVisible() { + page.setContent("

\n" + + "
Hidden data0
\n" + + "
visible data1
\n" + + "
Hidden data1
\n" + + "
visible data2
\n" + + "
Hidden data2
\n" + + "
visible data3
\n" + + "
"); + Locator locator = page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).nth(1); + assertThat(locator).hasText("visible data2"); + assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(true)).getByText("data3")).hasText("visible data3"); + assertThat(page.locator(".item").filter(new Locator.FilterOptions().setVisible(false)).getByText("data1")).hasText("Hidden data1"); + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java index e1a3ce610..28facfd14 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import com.microsoft.playwright.options.Contrast; import com.microsoft.playwright.options.ForcedColors; import com.microsoft.playwright.options.ReducedMotion; import org.junit.jupiter.api.Test; @@ -171,4 +172,17 @@ void shouldEmulateForcedColors() { page.emulateMedia(new Page.EmulateMediaOptions().setForcedColors(null)); assertEquals(true, page.evaluate("() => matchMedia('(forced-colors: none)').matches")); } + + @Test + void shouldEmulateContrast() { + assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setContrast(Contrast.NO_PREFERENCE)); + assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches")); + assertEquals(false, page.evaluate("matchMedia('(prefers-contrast: more)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setContrast(Contrast.MORE)); + assertEquals(false, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches")); + assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: more)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().setContrast(null)); + assertEquals(true, page.evaluate("matchMedia('(prefers-contrast: no-preference)').matches")); + } } diff --git a/playwright/src/test/resources/to-do-notifications/LICENSE b/playwright/src/test/resources/to-do-notifications/LICENSE new file mode 100644 index 000000000..3bbbc1ee9 --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + \ No newline at end of file diff --git a/playwright/src/test/resources/to-do-notifications/README.md b/playwright/src/test/resources/to-do-notifications/README.md new file mode 100644 index 000000000..5bd8b1d38 --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/README.md @@ -0,0 +1 @@ +Source: https://github.com/mdn/dom-examples/tree/main/to-do-notifications diff --git a/playwright/src/test/resources/to-do-notifications/index.html b/playwright/src/test/resources/to-do-notifications/index.html new file mode 100644 index 000000000..e9cd52c7b --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/index.html @@ -0,0 +1,108 @@ + + + + + + + To-do list with Notifications + + + + +

To-do list

+ +
+ +
    + +
+ +
+ +
+

Add new to-do item.

+ +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
    + +
+ + +
+ + + \ No newline at end of file diff --git a/playwright/src/test/resources/to-do-notifications/manifest.webapp b/playwright/src/test/resources/to-do-notifications/manifest.webapp new file mode 100644 index 000000000..fe48e2e42 --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/manifest.webapp @@ -0,0 +1,18 @@ +{ + "version": "0.1", + "name": "To-do list", + "description": "Store to-do items on your device, and be notified when the deadlines are up.", + "launch_path": "/to-do-notifications/index.html", + "icons": { + "128": "/to-do-notifications/img/icon-128.png" + }, + "developer": { + "name": "Chris Mills", + "url": "http://chrisdavidmills.github.io/to-do-notifications/" + }, + "permissions": { + "desktop-notification": { + "description": "Needed for creating system notifications." + } + } +} \ No newline at end of file diff --git a/playwright/src/test/resources/to-do-notifications/scripts/todo.js b/playwright/src/test/resources/to-do-notifications/scripts/todo.js new file mode 100644 index 000000000..2e3cfae21 --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/scripts/todo.js @@ -0,0 +1,354 @@ +window.onload = () => { + const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + // Hold an instance of a db object for us to store the IndexedDB data in + let db; + + // Create a reference to the notifications list in the bottom of the app; we will write database messages into this list by + // appending list items as children of this element + const note = document.getElementById('notifications'); + + // All other UI elements we need for the app + const taskList = document.getElementById('task-list'); + const taskForm = document.getElementById('task-form'); + const title = document.getElementById('title'); + const hours = document.getElementById('deadline-hours'); + const minutes = document.getElementById('deadline-minutes'); + const day = document.getElementById('deadline-day'); + const month = document.getElementById('deadline-month'); + const year = document.getElementById('deadline-year'); + const notificationBtn = document.getElementById('enable'); + + // Do an initial check to see what the notification permission state is + if (Notification.permission === 'denied' || Notification.permission === 'default') { + notificationBtn.style.display = 'block'; + } else { + notificationBtn.style.display = 'none'; + } + + note.appendChild(createListItem('App initialised.')); + + // Let us open our database + const DBOpenRequest = window.indexedDB.open('toDoList', 4); + + // Register two event handlers to act on the database being opened successfully, or not + DBOpenRequest.onerror = (event) => { + note.appendChild(createListItem('Error loading database.')); + }; + + DBOpenRequest.onsuccess = (event) => { + note.appendChild(createListItem('Database initialised.')); + + // Store the result of opening the database in the db variable. This is used a lot below + db = DBOpenRequest.result; + + // Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB + displayData(); + }; + + // This event handles the event whereby a new version of the database needs to be created + // Either one has not been created before, or a new version number has been submitted via the + // window.indexedDB.open line above + //it is only implemented in recent browsers + DBOpenRequest.onupgradeneeded = (event) => { + db = event.target.result; + + db.onerror = (event) => { + note.appendChild(createListItem('Error loading database.')); + }; + + // Create an objectStore for this database + const objectStore = db.createObjectStore('toDoList', { keyPath: 'taskTitle' }); + + // Define what data items the objectStore will contain + objectStore.createIndex('hours', 'hours', { unique: false }); + objectStore.createIndex('minutes', 'minutes', { unique: false }); + objectStore.createIndex('day', 'day', { unique: false }); + objectStore.createIndex('month', 'month', { unique: false }); + objectStore.createIndex('year', 'year', { unique: false }); + + objectStore.createIndex('notified', 'notified', { unique: false }); + + note.appendChild(createListItem('Object store created.')); + }; + + function displayData() { + // First clear the content of the task list so that you don't get a huge long list of duplicate stuff each time + // the display is updated. + while (taskList.firstChild) { + taskList.removeChild(taskList.lastChild); + } + + // Open our object store and then get a cursor list of all the different data items in the IDB to iterate through + const objectStore = db.transaction('toDoList').objectStore('toDoList'); + objectStore.openCursor().onsuccess = (event) => { + const cursor = event.target.result; + // Check if there are no (more) cursor items to iterate through + if (!cursor) { + // No more items to iterate through, we quit. + note.appendChild(createListItem('Entries all displayed.')); + return; + } + + // Check which suffix the deadline day of the month needs + const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value; + const ordDay = ordinal(day); + + // Build the to-do list entry and put it into the list item. + const toDoText = `${taskTitle} — ${hours}:${minutes}, ${month} ${ordDay} ${year}.`; + const listItem = createListItem(toDoText); + + if (notified === 'yes') { + listItem.style.textDecoration = 'line-through'; + listItem.style.color = 'rgba(255, 0, 0, 0.5)'; + } + + // Put the item item inside the task list + taskList.appendChild(listItem); + + // Create a delete button inside each list item, + const deleteButton = document.createElement('button'); + listItem.appendChild(deleteButton); + deleteButton.textContent = 'X'; + + // Set a data attribute on our delete button to associate the task it relates to. + deleteButton.setAttribute('data-task', taskTitle); + + // Associate action (deletion) when clicked + deleteButton.onclick = (event) => { + deleteItem(event); + }; + + // continue on to the next item in the cursor + cursor.continue(); + }; + }; + + // Add listener for clicking the submit button + taskForm.addEventListener('submit', addData, false); + + function addData(e) { + // Prevent default, as we don't want the form to submit in the conventional way + e.preventDefault(); + + // Stop the form submitting if any values are left empty. + // This should never happen as there is the required attribute + if (title.value === '' || hours.value === null || minutes.value === null || day.value === '' || month.value === '' || year.value === null) { + note.appendChild(createListItem('Data not submitted — form incomplete.')); + return; + } + + // Grab the values entered into the form fields and store them in an object ready for being inserted into the IndexedDB + const newItem = [ + { taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: 'no' }, + ]; + + // Open a read/write DB transaction, ready for adding the data + const transaction = db.transaction(['toDoList'], 'readwrite'); + + // Report on the success of the transaction completing, when everything is done + transaction.oncomplete = () => { + note.appendChild(createListItem('Transaction completed: database modification finished.')); + + // Update the display of data to show the newly added item, by running displayData() again. + displayData(); + }; + + // Handler for any unexpected error + transaction.onerror = () => { + note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`)); + }; + + // Call an object store that's already been added to the database + const objectStore = transaction.objectStore('toDoList'); + console.log(objectStore.indexNames); + console.log(objectStore.keyPath); + console.log(objectStore.name); + console.log(objectStore.transaction); + console.log(objectStore.autoIncrement); + + // Make a request to add our newItem object to the object store + const objectStoreRequest = objectStore.add(newItem[0]); + objectStoreRequest.onsuccess = (event) => { + + // Report the success of our request + // (to detect whether it has been succesfully + // added to the database, you'd look at transaction.oncomplete) + note.appendChild(createListItem('Request successful.')); + + // Clear the form, ready for adding the next entry + title.value = ''; + hours.value = null; + minutes.value = null; + day.value = 01; + month.value = 'January'; + year.value = 2020; + }; + }; + + function deleteItem(event) { + // Retrieve the name of the task we want to delete + const dataTask = event.target.getAttribute('data-task'); + + // Open a database transaction and delete the task, finding it by the name we retrieved above + const transaction = db.transaction(['toDoList'], 'readwrite'); + transaction.objectStore('toDoList').delete(dataTask); + + // Report that the data item has been deleted + transaction.oncomplete = () => { + // Delete the parent of the button, which is the list item, so it is no longer displayed + event.target.parentNode.parentNode.removeChild(event.target.parentNode); + note.appendChild(createListItem(`Task "${dataTask}" deleted.`)); + }; + }; + + // Check whether the deadline for each task is up or not, and responds appropriately + function checkDeadlines() { + // First of all check whether notifications are enabled or denied + if (Notification.permission === 'denied' || Notification.permission === 'default') { + notificationBtn.style.display = 'block'; + } else { + notificationBtn.style.display = 'none'; + } + + // Grab the current time and date + const now = new Date(); + + // From the now variable, store the current minutes, hours, day of the month, month, year and seconds + const minuteCheck = now.getMinutes(); + const hourCheck = now.getHours(); + const dayCheck = now.getDate(); // Do not use getDay() that returns the day of the week, 1 to 7 + const monthCheck = now.getMonth(); + const yearCheck = now.getFullYear(); // Do not use getYear() that is deprecated. + + // Open a new transaction + const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList'); + + // Open a cursor to iterate through all the data items in the IndexedDB + objectStore.openCursor().onsuccess = (event) => { + const cursor = event.target.result; + if (!cursor) return; + const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value; + + // convert the month names we have installed in the IDB into a month number that JavaScript will understand. + // The JavaScript date object creates month values as a number between 0 and 11. + const monthNumber = MONTHS.indexOf(month); + if (monthNumber === -1) throw new Error('Incorrect month entered in database.'); + + // Check if the current hours, minutes, day, month and year values match the stored values for each task. + // The parseInt() function transforms the value from a string to a number for comparison + // (taking care of leading zeros, and removing spaces and underscores from the string). + let matched = parseInt(hours) === hourCheck; + matched &&= parseInt(minutes) === minuteCheck; + matched &&= parseInt(day) === dayCheck; + matched &&= parseInt(monthNumber) === monthCheck; + matched &&= parseInt(year) === yearCheck; + if (matched && notified === 'no') { + // If the numbers all do match, run the createNotification() function to create a system notification + // but only if the permission is set + if (Notification.permission === 'granted') { + createNotification(taskTitle); + } + } + + // Move on to the next cursor item + cursor.continue(); + }; + }; + + // Ask for permission when the 'Enable notifications' button is clicked + function askNotificationPermission() { + // Function to actually ask the permissions + function handlePermission(permission) { + // Whatever the user answers, we make sure Chrome stores the information + if (!Reflect.has(Notification, 'permission')) { + Notification.permission = permission; + } + + // Set the button to shown or hidden, depending on what the user answers + if (Notification.permission === 'denied' || Notification.permission === 'default') { + notificationBtn.style.display = 'block'; + } else { + notificationBtn.style.display = 'none'; + } + }; + + // Check if the browser supports notifications + if (!Reflect.has(window, 'Notification')) { + console.log('This browser does not support notifications.'); + } else { + if (checkNotificationPromise()) { + Notification.requestPermission().then(handlePermission); + } else { + Notification.requestPermission(handlePermission); + } + } + }; + + // Check whether browser supports the promise version of requestPermission() + // Safari only supports the old callback-based version + function checkNotificationPromise() { + try { + Notification.requestPermission().then(); + } catch(e) { + return false; + } + + return true; + }; + + // Wire up notification permission functionality to 'Enable notifications' button + notificationBtn.addEventListener('click', askNotificationPermission); + + function createListItem(contents) { + const listItem = document.createElement('li'); + listItem.textContent = contents; + return listItem; + }; + + // Create a notification with the given title + function createNotification(title) { + // Create and show the notification + const img = '/to-do-notifications/img/icon-128.png'; + const text = `HEY! Your task "${title}" is now overdue.`; + const notification = new Notification('To do list', { body: text, icon: img }); + + // We need to update the value of notified to 'yes' in this particular data object, so the + // notification won't be set off on it again + + // First open up a transaction + const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList'); + + // Get the to-do list object that has this title as its title + const objectStoreTitleRequest = objectStore.get(title); + + objectStoreTitleRequest.onsuccess = () => { + // Grab the data object returned as the result + const data = objectStoreTitleRequest.result; + + // Update the notified value in the object to 'yes' + data.notified = 'yes'; + + // Create another request that inserts the item back into the database + const updateTitleRequest = objectStore.put(data); + + // When this new request succeeds, run the displayData() function again to update the display + updateTitleRequest.onsuccess = () => { + displayData(); + }; + }; + }; + + // Using a setInterval to run the checkDeadlines() function every second + setInterval(checkDeadlines, 1000); +} + +// Helper function returning the day of the month followed by an ordinal (st, nd, or rd) +function ordinal(day) { + const n = day.toString(); + const last = n.slice(-1); + if (last === '1' && n !== '11') return `${n}st`; + if (last === '2' && n !== '12') return `${n}nd`; + if (last === '3' && n !== '13') return `${n}rd`; + return `${n}th`; +}; \ No newline at end of file diff --git a/playwright/src/test/resources/to-do-notifications/style/style.css b/playwright/src/test/resources/to-do-notifications/style/style.css new file mode 100644 index 000000000..c08ff299a --- /dev/null +++ b/playwright/src/test/resources/to-do-notifications/style/style.css @@ -0,0 +1,248 @@ +/* Basic set up + sizing for containers */ + +html, +body { + margin: 0; +} + +html { + width: 100%; + height: 100%; + font-size: 10px; + font-family: Georgia, "Times New Roman", Times, serif; + background: #111; +} + +body { + width: 50rem; + position: relative; + background: #d88; + margin: 0 auto; + border-left: 2px solid #d33; + border-right: 2px solid #d33; +} + +h1, +h2 { + text-align: center; + background: #d88; + font-family: Arial, Helvetica, sans-serif; +} + +h1 { + font-size: 6rem; + margin: 0; + background: #d66; +} + +h2 { + font-size: 2.4rem; +} + +/* Bottom toolbar styling */ + +#toolbar { + position: relative; + height: 6rem; + width: 100%; + background: #d66; + border-top: 2px solid #d33; + border-bottom: 2px solid #d33; +} + +#enable, +input[type="submit"] { + line-height: 1.8; + font-size: 1.3rem; + border-radius: 5px; + border: 1px solid black; + color: black; + text-shadow: 1px 1px 1px black; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: + inset 0px 5px 3px rgba(255, 255, 255, 0.2), + inset 0px -5px 3px rgba(0, 0, 0, 0.2); +} + +#enable { + position: absolute; + bottom: 0.3rem; + right: 0.3rem; +} + +#notifications { + margin: 0; + position: relative; + padding: 0.3rem; + background: #ddd; + position: absolute; + top: 0rem; + left: 0rem; + height: 5.4rem; + width: 50%; + overflow: auto; + line-height: 1.2; +} + +#notifications li { + margin-left: 1.5rem; +} + +/* New item form styling */ + +.form-box { + background: #d66; + width: 85%; + padding: 1rem; + margin: 2rem auto; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7); +} + +form div { + margin-bottom: 1rem; +} + +form .full-width { + margin: 1rem auto 2rem; + width: 100%; +} + +form .half-width { + width: 50%; + float: left; +} + +form .third-width { + width: 33%; + float: left; +} + +form div label { + width: 10rem; + float: left; + padding-right: 1rem; + font-size: 1.6rem; + line-height: 1.6; +} + +form .full-width input { + width: 30rem; +} + +form .half-width input { + width: 8.75rem; +} + +form .third-width select { + width: 13.5rem; +} + +form div input[type="submit"] { + clear: both; + width: 20rem; + display: block; + height: 3rem; + margin: 0 auto; + position: relative; + top: 0.5rem; +} + +/* || tasks box */ + +.task-box { + width: 85%; + padding: 1rem; + margin: 2rem auto; + font-size: 1.8rem; +} + +.task-box ul { + margin: 0; + padding: 0; +} + +.task-box li { + list-style-type: none; + padding: 1rem; + border-bottom: 2px solid #d33; +} + +.task-box li:last-child { + border-bottom: none; +} + +.task-box li:last-child { + margin-bottom: 0rem; +} + +.task-box button { + margin-left: 2rem; + font-size: 1.6rem; + border: 1px solid #eee; + border-radius: 5px; + box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.5) 1px 1px 1px black; +} + +/* setting cursor for interactive controls */ + +button, +input[type="submit"], +select { + cursor: pointer; +} + +/* media query for small screens */ + +@media (max-width: 32rem) { + body { + width: 100%; + border-left: none; + border-right: none; + } + + form div { + clear: both; + } + + form .full-width { + margin: 1rem auto; + } + + form .half-width { + width: 100%; + float: none; + } + + form .third-width { + width: 100%; + float: none; + } + + form div label { + width: 36%; + padding-left: 1rem; + } + + form input, + form select, + form label { + line-height: 2.5rem; + font-size: 2rem; + } + + form .full-width input { + width: 50%; + } + + form .half-width input { + width: 50%; + } + + form .third-width select { + width: 50%; + } + + #enable { + right: 1rem; + } +} \ No newline at end of file diff --git a/scripts/DRIVER_VERSION b/scripts/DRIVER_VERSION index 41fda7934..ba0a71911 100644 --- a/scripts/DRIVER_VERSION +++ b/scripts/DRIVER_VERSION @@ -1 +1 @@ -1.50.1-beta-1738592302000 +1.51.0 From 9a59c50a464a0b45dd0799536542569fe95fd7a2 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 6 Mar 2025 14:39:02 -0800 Subject: [PATCH 2/5] skip failing test, update actions/cache to v4 --- .github/workflows/test_cli.yml | 2 +- .../test/java/com/microsoft/playwright/TestPageScreenshot.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_cli.yml b/.github/workflows/test_cli.yml index 9b877de15..d04887be9 100644 --- a/.github/workflows/test_cli.yml +++ b/.github/workflows/test_cli.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Cache Maven packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java index 795ed35fc..a756476b3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageScreenshot.java @@ -253,7 +253,7 @@ void shouldWorkWhenMaskColorIsNotPinkF0F() { } static boolean isScreenshotTestDisabled() { - if (isWebKit()) { + if (isWebKit() || isChromium()) { // Array lengths differ. return true; } From e41c5041f4ebe5240ff47c0eb51e53eb9fb5f5a6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 6 Mar 2025 17:02:16 -0800 Subject: [PATCH 3/5] skip relative dir test on windows --- .../playwright/TestBrowserContextCookies.java | 12 ++++-------- .../playwright/TestDefaultBrowserContext2.java | 5 +++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java index a61ee5d87..5f399d7ae 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java @@ -31,6 +31,10 @@ import static org.junit.jupiter.api.Assertions.*; public class TestBrowserContextCookies extends TestBase { + static boolean isWebkitWindows() { + return isWebKit() && isWindows; + } + @Test void shouldGetACookie() { page.navigate(server.EMPTY_PAGE); @@ -96,10 +100,6 @@ void shouldProperlyReportHttpOnlyCookie() { assertTrue(cookies.get(0).httpOnly); } - static boolean isWebKitWindows() { - return isWebKit() && getOS() == Utils.OS.WINDOWS; - } - @Test @DisabledIf(value="isWebKitWindows", disabledReason="fail") void shouldProperlyReportStrictSameSiteCookie() { @@ -192,10 +192,6 @@ void shouldGetCookiesFromMultipleUrls() { "}]", cookies); } - static boolean isWebkitWindows() { - return isWebKit() && isWindows; - } - @Test @DisabledIf(value="isWebkitWindows", disabledReason="Same site is not implemented in curl") void shouldAcceptSameSiteAttribute() { diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java index 6716365dc..d622696c3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java @@ -293,7 +293,12 @@ void shouldSupportContrastOption() { assertEquals(false, page.evaluate("() => matchMedia('(prefers-contrast: no-preference)').matches")); } + static boolean tempDirCanBeOnDifferentRoot() { + return isMac; + } + @Test + @DisabledIf(value="tempDirCanBeOnDifferentRoot", disabledReason="IllegalArgument 'other' has different root on GitHub Actions.") void shouldAcceptRelativeUserDataDir(@TempDir Path tmpDir) throws Exception { Path userDataDir = tempDir.resolve("user-data-dir"); Path cwd = Paths.get("").toAbsolutePath(); From a4caa37f19812d53b0c89ac00d83a0729c0c2db8 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 7 Mar 2025 08:11:31 -0800 Subject: [PATCH 4/5] revert isWebkitWindows changes --- .../playwright/TestBrowserContextCookies.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java index 5f399d7ae..a61ee5d87 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java @@ -31,10 +31,6 @@ import static org.junit.jupiter.api.Assertions.*; public class TestBrowserContextCookies extends TestBase { - static boolean isWebkitWindows() { - return isWebKit() && isWindows; - } - @Test void shouldGetACookie() { page.navigate(server.EMPTY_PAGE); @@ -100,6 +96,10 @@ void shouldProperlyReportHttpOnlyCookie() { assertTrue(cookies.get(0).httpOnly); } + static boolean isWebKitWindows() { + return isWebKit() && getOS() == Utils.OS.WINDOWS; + } + @Test @DisabledIf(value="isWebKitWindows", disabledReason="fail") void shouldProperlyReportStrictSameSiteCookie() { @@ -192,6 +192,10 @@ void shouldGetCookiesFromMultipleUrls() { "}]", cookies); } + static boolean isWebkitWindows() { + return isWebKit() && isWindows; + } + @Test @DisabledIf(value="isWebkitWindows", disabledReason="Same site is not implemented in curl") void shouldAcceptSameSiteAttribute() { From 61cf2584848e27f461542c746e75bdf465173c9a Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 7 Mar 2025 18:31:44 -0800 Subject: [PATCH 5/5] Update condition --- .../com/microsoft/playwright/TestDefaultBrowserContext2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java index d622696c3..41b85535a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java @@ -294,7 +294,7 @@ void shouldSupportContrastOption() { } static boolean tempDirCanBeOnDifferentRoot() { - return isMac; + return isWindows; } @Test