1
0
mirror of synced 2026-05-23 19:23:20 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Yury Semikhatsky 4307548995 chore: mark-1.51.0 (#1769) 2025-03-17 12:13:56 -07:00
121 changed files with 2198 additions and 2426 deletions
+3 -7
View File
@@ -1,10 +1,6 @@
trigger: none
pr: none
trigger:
tags:
include:
- '*'
resources:
repositories:
- repository: 1esPipelines
@@ -37,8 +33,8 @@ extends:
artifact: esrp-build
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^v1\..* ]]; then
echo "Can only publish from a release tag branch (v1.*)."
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
echo "Can only publish from a release branch."
echo "Unexpected branch name: $CURRENT_BRANCH"
exit 1
fi
+3 -6
View File
@@ -20,19 +20,16 @@ jobs:
test:
name: Test
timeout-minutes: 120
runs-on: ${{ matrix.runs-on }}
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Test
run: |
CONTAINER_ID="$(docker run --rm -e CI --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
@@ -0,0 +1,21 @@
name: "Internal Tests"
on:
push:
branches:
- main
- release-*
jobs:
trigger:
name: "trigger"
runs-on: ubuntu-24.04
steps:
- run: |
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GH_TOKEN}" \
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
https://api.github.com/repos/microsoft/playwright-browsers/dispatches
env:
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
-3
View File
@@ -1,3 +0,0 @@
path_classifiers:
tests:
- "playwright/src/test/**"
+3 -3
View File
@@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->139.0.7258.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->140.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->134.0.6998.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->135.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
## Documentation
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.54.0</version>
<version>1.51.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.54.0</version>
<version>1.51.0</version>
</parent>
<artifactId>driver</artifactId>
+2 -2
View File
@@ -6,11 +6,11 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.54.0</version>
<version>1.51.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.54.0</playwright.version>
<playwright.version>1.51.0</playwright.version>
</properties>
<dependencies>
<dependency>
+1 -10
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.54.0</version>
<version>1.51.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -57,15 +57,6 @@
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</dependency>
<!--
The following slf4j-simple dependency resolves the warning:
'SLF4J(W): No SLF4J providers were found.'
This warning is produced by the org.java-websocket library.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
@@ -72,12 +72,6 @@ public interface APIRequest {
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public Integer maxRedirects;
/**
* Network proxy settings.
*/
@@ -177,15 +171,6 @@ public interface APIRequest {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public NewContextOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
/**
* Network proxy settings.
*/
@@ -411,6 +411,8 @@ public interface BrowserContext extends AutoCloseable {
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*
* <p> <strong>NOTE:</strong> IndexedDBs with typed arrays are currently not supported.
*/
public Boolean indexedDB;
/**
@@ -423,6 +425,8 @@ public interface BrowserContext extends AutoCloseable {
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*
* <p> <strong>NOTE:</strong> IndexedDBs with typed arrays are currently not supported.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
@@ -596,8 +600,7 @@ public interface BrowserContext extends AutoCloseable {
*/
List<Page> backgroundPages();
/**
* Gets the browser instance that owns the context. Returns {@code null} if the context is created outside of normal
* browser, e.g. Android or Electron.
* Returns the browser instance of the context. If it was launched as a persistent context null gets returned.
*
* @since v1.8
*/
@@ -872,7 +875,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul>
* @since v1.8
*/
@@ -905,7 +907,6 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "local-fonts"}</li>
* </ul>
* @since v1.8
*/
@@ -995,8 +996,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1051,8 +1052,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1105,8 +1106,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1161,8 +1162,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1215,8 +1216,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -1271,8 +1272,8 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -209,9 +209,6 @@ public interface BrowserType {
/**
* 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>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public Map<String, Object> firefoxUserPrefs;
/**
@@ -229,8 +226,8 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
@@ -342,9 +339,6 @@ public interface BrowserType {
/**
* 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>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
@@ -374,8 +368,8 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -541,9 +535,6 @@ public interface BrowserType {
/**
* 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>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public Map<String, Object> firefoxUserPrefs;
/**
@@ -573,8 +564,8 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
@@ -890,9 +881,6 @@ public interface BrowserType {
/**
* 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>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
@@ -946,8 +934,8 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true} unless
* the {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -1377,15 +1365,11 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
@@ -1397,15 +1381,11 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
@@ -600,14 +600,18 @@ public interface Locator {
}
class EvaluateOptions {
/**
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public EvaluateOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -616,14 +620,18 @@ public interface Locator {
}
class EvaluateHandleOptions {
/**
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public EvaluateHandleOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -2602,20 +2610,6 @@ public interface Locator {
* @since v1.14
*/
void dblclick(DblclickOptions options);
/**
* Describes the locator, description is used in the trace viewer and reports. Returns the locator pointing to the same
* element.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByTestId("btn-sub").describe("Subscribe button");
* button.click();
* }</pre>
*
* @param description Locator description.
* @since v1.53
*/
Locator describe(String description);
/**
* Programmatically dispatch an event on the matching element.
*
@@ -2862,14 +2856,6 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
@@ -2894,14 +2880,6 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.14
@@ -2925,14 +2903,6 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
@@ -21,11 +21,6 @@ import com.microsoft.playwright.options.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
*
* <p> <strong>NOTE:</strong> If you want to debug where the mouse moved, you can use the <a
* href="https://playwright.dev/java/docs/trace-viewer-intro">Trace viewer</a> or <a
* href="https://playwright.dev/java/docs/running-tests">Playwright Inspector</a>. A red dot showing the location of the
* mouse will be shown for every mouse action.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
@@ -6317,8 +6317,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -6376,8 +6376,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -6433,8 +6433,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -6492,8 +6492,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -6549,8 +6549,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -6608,8 +6608,8 @@ public interface Page extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
@@ -370,10 +370,6 @@ public interface Route {
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
default void resume() {
@@ -402,10 +398,6 @@ public interface Route {
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
void resume(ResumeOptions options);
@@ -23,12 +23,6 @@ import java.nio.file.Path;
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
* href="https://playwright.dev/java/docs/trace-viewer">Trace Viewer</a> after Playwright script runs.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code context.tracing}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code
* Browser browser = chromium.launch();
@@ -206,12 +200,6 @@ public interface Tracing {
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
@@ -231,12 +219,6 @@ public interface Tracing {
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
@@ -27,7 +27,7 @@ import java.util.function.Consumer;
*
* <p> <strong>Mocking</strong>
*
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over the
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over the
* WebSocket. Here is an example that responds to a {@code "request"} with a {@code "response"}.
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
@@ -16,7 +16,6 @@
package com.microsoft.playwright.assertions;
import java.util.*;
import java.util.regex.Pattern;
import com.microsoft.playwright.options.AriaRole;
@@ -238,20 +237,6 @@ public interface LocatorAssertions {
return this;
}
}
class ContainsClassOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public ContainsClassOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ContainsTextOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -870,98 +855,6 @@ public interface LocatorAssertions {
* @since v1.20
*/
void isVisible(IsVisibleOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(String expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(String expected, ContainsClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(List<String> expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(new String[] {"inactive", "active", "inactive"});
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(List<String> expected, ContainsClassOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. All nested elements will be considered
* when computing the text content of the element. You can use regular expressions for the value as well.
@@ -1552,20 +1445,19 @@ public interface LocatorAssertions {
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1576,20 +1468,19 @@ public interface LocatorAssertions {
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1598,20 +1489,19 @@ public interface LocatorAssertions {
void hasClass(String expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1622,20 +1512,19 @@ public interface LocatorAssertions {
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1644,20 +1533,19 @@ public interface LocatorAssertions {
void hasClass(Pattern expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1668,20 +1556,19 @@ public interface LocatorAssertions {
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1690,20 +1577,19 @@ public interface LocatorAssertions {
void hasClass(String[] expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1714,20 +1600,19 @@ public interface LocatorAssertions {
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
* the element's {@code class} attribute. To match individual classes or perform partial matches, use a regular expression:
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* assertThat(page.locator("#component")).hasClass("middle selected row");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -39,8 +39,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
private String disposeReason;
protected TimeoutSettings timeoutSettings = new TimeoutSettings();
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
@@ -53,17 +51,21 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@Override
public void dispose(DisposeOptions options) {
withLogging("APIRequestContext.dispose", () -> disposeImpl(options));
}
private void disposeImpl(DisposeOptions options) {
if (options == null) {
options = new DisposeOptions();
}
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params, NO_TIMEOUT);
sendMessage("dispose", params);
}
@Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return fetchImpl(urlOrRequest, (RequestOptionsImpl) options);
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
}
@Override
@@ -91,7 +93,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options == null) {
options = new RequestOptionsImpl();
}
options.timeout = timeoutSettings.timeout(options.timeout);
JsonObject params = new JsonObject();
params.addProperty("url", url);
if (options.params != null) {
@@ -131,6 +132,9 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options.multipart != null) {
params.add("multipartData", serializeMultipartData(options.multipart.fields));
}
if (options.timeout != null) {
params.addProperty("timeout", options.timeout);
}
if (options.failOnStatusCode != null) {
params.addProperty("failOnStatusCode", options.failOnStatusCode);
}
@@ -149,7 +153,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
params.addProperty("maxRetries", options.maxRetries);
}
JsonObject json = sendMessage("fetch", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
@@ -215,12 +219,14 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@Override
public String storageState(StorageStateOptions options) {
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("APIRequestContext.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;
});
}
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
@@ -17,6 +17,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
@@ -25,11 +26,12 @@ import com.microsoft.playwright.options.ClientCertificate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static java.nio.file.Files.readAllBytes;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
@@ -40,6 +42,10 @@ class APIRequestImpl implements APIRequest {
@Override
public APIRequestContextImpl newContext(NewContextOptions options) {
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
}
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
@@ -61,17 +67,13 @@ class APIRequestImpl implements APIRequest {
}
List<ClientCertificate> clientCertificateList = options.clientCertificates;
options.clientCertificates = null;
Double timeout = options.timeout;
// Timeout is handled on the client.
options.timeout = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
addToProtocol(params, clientCertificateList);
JsonObject result = playwright.sendMessage("newRequest", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
context.timeoutSettings.setDefaultTimeout(timeout);
return context;
}
}
@@ -17,6 +17,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.reflect.TypeToken;
import com.microsoft.playwright.APIResponse;
@@ -28,7 +29,6 @@ import java.util.Base64;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
@@ -49,7 +49,7 @@ class APIResponseImpl implements APIResponse {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
}
@@ -64,9 +64,11 @@ class APIResponseImpl implements APIResponse {
@Override
public void dispose() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params, NO_TIMEOUT);
context.withLogging("APIResponse.dispose", () -> {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params);
});
}
@Override
@@ -112,7 +114,7 @@ class APIResponseImpl implements APIResponse {
List<String> fetchLog() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
}
@@ -86,6 +86,6 @@ class ArtifactImpl extends ChannelOwner {
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params, NO_TIMEOUT);
sendMessage("saveAs", params);
}
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
@@ -28,26 +29,28 @@ import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList;
abstract class AssertionsBase {
class AssertionsBase {
final LocatorImpl actualLocator;
final boolean isNot;
AssertionsBase(boolean isNot) {
AssertionsBase(LocatorImpl actual, boolean isNot) {
this.actualLocator = actual;
this.isNot = isNot;
}
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options, String title) {
expectImpl(expression, asList(textValue), expected, message, options, title);
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
expectImpl(expression, asList(textValue), expected, message, options);
}
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options, String title) {
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
expectImpl(expression, options, expected, message, title);
expectImpl(expression, options, expected, message);
}
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message, String title) {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
@@ -55,7 +58,7 @@ abstract class AssertionsBase {
if (isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = doExpect(expression, expectOptions, title);
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = (result.log == null) ? "" : String.join("\n", result.log);
@@ -72,9 +75,7 @@ abstract class AssertionsBase {
}
}
abstract FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title);
protected static ValueWrapper formatValue(Object value) {
private static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value);
}
@@ -78,11 +78,11 @@ class BindingCall extends ChannelOwner {
JsonObject params = new JsonObject();
params.add("result", gson().toJsonTree(serializeArgument(result)));
sendMessage("resolve", params, NO_TIMEOUT);
sendMessage("resolve", params);
} catch (RuntimeException exception) {
JsonObject params = new JsonObject();
params.add("error", gson().toJsonTree(serializeError(exception)));
sendMessage("reject", params, NO_TIMEOUT);
sendMessage("reject", params);
}
}
}
@@ -42,7 +42,7 @@ import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
protected BrowserImpl browser;
private final BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
private final ClockImpl clock;
@@ -51,7 +51,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
final Router routes = new Router();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closingOrClosed;
private boolean closeWasCalled;
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
@@ -69,6 +69,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
final Map<String, HarRecorder> harRecorders = new HashMap<>();
static class HarRecorder {
@@ -96,30 +98,29 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
} else {
browser = null;
}
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
Path videosDir() {
JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
if (recordVideo == null) {
return null;
void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
}
return Paths.get(recordVideo.get("dir").getAsString());
}
URL baseUrl() {
JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
if (url != null) {
try {
return new URL(url.getAsString());
} catch (MalformedURLException e) {
}
void setBaseUrl(String spec) {
try {
this.baseUrl = new URL(spec);
} catch (MalformedURLException e) {
this.baseUrl = null;
}
return null;
}
String effectiveCloseReason() {
@@ -261,7 +262,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Page page) {
JsonObject params = new JsonObject();
params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@@ -269,14 +270,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Frame frame) {
JsonObject params = new JsonObject();
params.add("frame", ((FrameImpl) frame).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@Override
public void close(CloseOptions options) {
if (!closingOrClosed) {
closingOrClosed = true;
withLogging("BrowserContext.close", () -> closeImpl(options));
}
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) {
options = new CloseOptions();
}
@@ -285,7 +295,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("harExport", params).getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue();
@@ -297,46 +307,42 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject unzipParams = new JsonObject();
unzipParams.addProperty("zipFile", tmpPath);
unzipParams.addProperty("harFile", harParams.path.toString());
connection.localUtils.sendMessage("harUnzip", unzipParams, NO_TIMEOUT);
connection.localUtils.sendMessage("harUnzip", unzipParams);
} else {
artifact.saveAs(harParams.path);
}
artifact.delete();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params, NO_TIMEOUT);
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
@Override
public void addCookies(List<Cookie> cookies) {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params, NO_TIMEOUT);
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params);
});
}
@Override
public void addInitScript(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params, NO_TIMEOUT);
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script));
}
@Override
public void addInitScript(Path path) {
try {
byte[] bytes = readAllBytes(path);
addInitScript(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
withLogging("BrowserContext.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
});
}
@Override
@@ -344,6 +350,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return new ArrayList<>(backgroundPages);
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params);
}
@Override
public BrowserImpl browser() {
return browser;
@@ -351,6 +363,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void clearCookies(ClearCookiesOptions options) {
withLogging("BrowserContext.clearCookies", () -> clearCookiesImpl(options));
}
private void clearCookiesImpl(ClearCookiesOptions options) {
if (options == null) {
options = new ClearCookiesOptions();
}
@@ -358,7 +374,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
setStringOrRegex(params, "name", options.name);
setStringOrRegex(params, "domain", options.domain);
setStringOrRegex(params, "path", options.path);
sendMessage("clearCookies", params, NO_TIMEOUT);
sendMessage("clearCookies", params);
}
private static void setStringOrRegex(JsonObject params, String name, Object value) {
@@ -373,24 +389,28 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void clearPermissions() {
sendMessage("clearPermissions");
withLogging("BrowserContext.clearPermissions", () -> sendMessage("clearPermissions"));
}
@Override
public List<Cookie> cookies(List<String> urls) {
return withLogging("BrowserContext.cookies", () -> cookiesImpl(urls));
}
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = new ArrayList<>();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return asList(cookies);
}
@Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
exposeBindingImpl(name, playwrightBinding, options);
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
@@ -409,16 +429,21 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true);
}
sendMessage("exposeBinding", params, NO_TIMEOUT);
sendMessage("exposeBinding", params);
}
@Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
withLogging("BrowserContext.exposeFunction",
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
public void grantPermissions(List<String> permissions, GrantPermissionsOptions options) {
withLogging("BrowserContext.grantPermissions", () -> grantPermissionsImpl(permissions, options));
}
private void grantPermissionsImpl(List<String> permissions, GrantPermissionsOptions options) {
if (options == null) {
options = new GrantPermissionsOptions();
}
@@ -427,11 +452,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
sendMessage("grantPermissions", params, NO_TIMEOUT);
sendMessage("grantPermissions", params);
}
@Override
public PageImpl newPage() {
return withLogging("BrowserContext.newPage", () -> newPageImpl());
}
private PageImpl newPageImpl() {
if (ownerPage != null) {
throw new PlaywrightException("Please use browser.newContext()");
}
@@ -451,7 +480,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, false), handler, options);
route(new UrlMatcher(baseUrl, url), handler, options);
}
@Override
@@ -470,23 +499,25 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
recordIntoHar(null, har, options, null);
recordIntoHar(null, har, options);
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
}
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
});
}
@Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
routeWebSocketImpl(new UrlMatcher(baseUrl, url), handler);
}
@Override
@@ -500,82 +531,108 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
withLogging("BrowserContext.routeWebSocket", () -> {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
});
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
if (contentPolicy == null) {
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);
}
if (contentPolicy == null) {
contentPolicy = HarContentPolicy.ATTACH;
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
JsonObject params = new JsonObject();
if (page != null) {
params.add("page", page.toProtocolRef());
}
JsonObject recordHarArgs = new JsonObject();
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
addHarUrlFilter(recordHarArgs, options.url);
params.add("options", recordHarArgs);
JsonObject json = sendMessage("harStart", params, NO_TIMEOUT).getAsJsonObject();
JsonObject jsonOptions = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
jsonOptions.addProperty("content", options.updateContent == null ?
HarContentPolicy.ATTACH.name().toLowerCase() :
options.updateContent.name().toLowerCase());
jsonOptions.addProperty("mode", options.updateMode == null ?
HarMode.MINIMAL.name().toLowerCase() :
options.updateMode.name().toLowerCase());
addHarUrlFilter(jsonOptions, options.url);
params.add("options", jsonOptions);
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
String harId = json.get("harId").getAsString();
harRecorders.put(harId, new HarRecorder(har, contentPolicy));
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
timeoutSettings.setDefaultNavigationTimeout(timeout);
setDefaultNavigationTimeoutImpl(timeout);
}
void setDefaultNavigationTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
}
@Override
public void setDefaultTimeout(double timeout) {
timeoutSettings.setDefaultTimeout(timeout);
setDefaultTimeoutImpl(timeout);
}
void setDefaultTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
}
@Override
public void setExtraHTTPHeaders(Map<String, String> headers) {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
withLogging("BrowserContext.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
});
}
@Override
public void setGeolocation(Geolocation geolocation) {
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params, NO_TIMEOUT);
withLogging("BrowserContext.setGeolocation", () -> {
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params);
});
}
@Override
public void setOffline(boolean offline) {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params, NO_TIMEOUT);
withLogging("BrowserContext.setOffline", () -> {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params);
});
}
@Override
public String storageState(StorageStateOptions options) {
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, NO_TIMEOUT);
JsonElement json = sendMessage("storageState", params);
String storageState = json.toString();
if (options.path != null) {
@@ -591,13 +648,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void unrouteAll() {
routes.removeAll();
updateInterceptionPatterns();
withLogging("BrowserContext.unrouteAll", () -> {
routes.removeAll();
updateInterceptionPatterns();
});
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
unroute(new UrlMatcher(this.baseUrl, url), handler);
}
@Override
@@ -643,16 +702,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
routes.remove(matcher, handler);
updateInterceptionPatterns();
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
updateInterceptionPatterns();
});
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns());
}
void handleRoute(RouteImpl route) {
@@ -805,10 +866,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
void didClose() {
closingOrClosed = true;
if (browser != null) {
browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
@@ -817,49 +876,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject params = new JsonObject();
params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
protected void initializeHarFromOptions(Browser.NewContextOptions options) {
if (options.recordHarPath == null) {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
return;
}
HarContentPolicy contentPolicy = options.recordHarContent;
if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();
if (options.recordHarUrlFilter instanceof String) {
routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
}
if (options.recordHarMode != null) {
routeFromHAROptions.updateMode = options.recordHarMode;
} else {
routeFromHAROptions.updateMode = HarMode.FULL;
}
routeFromHAROptions.url = options.recordHarUrlFilter;
recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
}
}
@@ -20,6 +20,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.HarContentPolicy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -28,8 +29,10 @@ import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
@@ -66,6 +69,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
public void close(CloseOptions options) {
withLogging("Browser.close", () -> closeImpl(options));
}
private void closeImpl(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
@@ -110,20 +117,16 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
public BrowserContextImpl newContext(NewContextOptions options) {
return withLogging("Browser.newContext", () -> newContextImpl(options));
}
private BrowserContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, NewContextOptions.class);
}
NewContextOptions harOptions = Utils.clone(options);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
@@ -138,10 +141,51 @@ class BrowserImpl extends ChannelOwner implements Browser {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject recordHar = null;
Path recordHarPath = options.recordHarPath;
HarContentPolicy harContentPolicy = null;
if (options.recordHarPath != null) {
recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarContent != null) {
harContentPolicy = options.recordHarContent;
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
harContentPolicy = HarContentPolicy.OMIT;
}
if (harContentPolicy != null) {
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
}
if (options.recordHarMode != null) {
recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
}
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
options.recordHarPath = null;
options.recordHarMode = null;
options.recordHarOmitContent = null;
options.recordHarContent = null;
options.recordHarUrlFilter = null;
} else {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
if (recordHar != null) {
params.add("recordHar", recordHar);
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -169,21 +213,31 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
params.add("selectorEngines", gson().toJsonTree(browserType.playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", browserType.playwright.selectors.testIdAttributeName);
JsonElement result = sendMessage("newContext", params, NO_TIMEOUT);
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.initializeHarFromOptions(harOptions);
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
if (launchOptions != null) {
context.tracing().setTracesDir(launchOptions.tracesDir);
}
contexts.add(context);
return context;
}
@Override
public Page newPage(NewPageOptions options) {
return withTitle("Create Page", () -> newPageImpl(options));
return withLogging("Browser.newPage", () -> newPageImpl(options));
}
@Override
public void startTracing(Page page, StartTracingOptions options) {
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
}
private void startTracingImpl(Page page, StartTracingOptions options) {
if (options == null) {
options = new StartTracingOptions();
}
@@ -192,11 +246,15 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef());
}
sendMessage("startTracing", params, NO_TIMEOUT);
sendMessage("startTracing", params);
}
@Override
public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
byte[] data = artifact.readAllBytes();
@@ -237,46 +295,18 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
void handleEvent(String event, JsonObject parameters) {
switch (event) {
case "context":
didCreateContext(connection.getExistingObject(parameters.getAsJsonObject("context").get("guid").getAsString()));
break;
case "close":
didClose();
break;
if ("close".equals(event)) {
didClose();
}
}
@Override
public CDPSession newBrowserCDPSession() {
JsonObject params = new JsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir){
// Note: when using connect(), `browserType` is different from `this.parent`.
// This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
this.browserType = browserType;
this.tracePath = tracesDir;
for (BrowserContextImpl context : contexts) {
context.tracing().setTracesDir(tracesDir);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didCreateContext(BrowserContextImpl context) {
context.browser = this;
contexts.add(context);
// Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
// and will be configured later in `ConnectToBrowserType`.
if (browserType != null) {
context.tracing().setTracesDir(tracePath);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
@@ -22,30 +22,34 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HarContentPolicy;
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;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
protected PlaywrightImpl playwright;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public BrowserImpl launch(LaunchOptions options) {
return withLogging("BrowserType.launch", () -> launchImpl(options));
}
private BrowserImpl launchImpl(LaunchOptions options) {
if (options == null) {
options = new LaunchOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params, TimeoutSettings.launchTimeout(options.timeout));
JsonElement result = sendMessage("launch", params);
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
browser.launchOptions = options;
@@ -54,6 +58,10 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override
public Browser connect(String wsEndpoint, ConnectOptions options) {
return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
}
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
if (options == null) {
options = new ConnectOptions();
}
@@ -76,12 +84,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
headers.addProperty("x-playwright-browser", name());
}
Double timeout = options.timeout;
if (timeout == null) {
timeout = 0.0;
}
JsonObject json = connection.localUtils().sendMessage("connect", params, timeout).getAsJsonObject();
JsonObject json = connection.localUtils().sendMessage("connect", params).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
PlaywrightImpl playwright = connection.initializePlaywright();
@@ -93,13 +96,14 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
}
playwright.selectors = this.playwright.selectors;
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isConnectedOverWebSocket = true;
browser.connectToBrowserType(this, null);
browser.browserType = this;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.unregisterSelectors();
pipe.offClose(connectionCloseListener);
try {
connection.close();
@@ -115,16 +119,25 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
}
return withLogging("BrowserType.connectOverCDP", () -> connectOverCDPImpl(endpointURL, options));
}
private Browser connectOverCDPImpl(String endpointURL, ConnectOverCDPOptions options) {
if (options == null) {
options = new ConnectOverCDPOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, null);
browser.browserType = this;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
return browser;
}
@@ -134,19 +147,54 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override
public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
return withLogging("BrowserType.launchPersistentContext",
() -> launchPersistentContextImpl(userDataDir, options));
}
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
if (options == null) {
options = new LaunchPersistentContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, LaunchPersistentContextOptions.class);
}
Browser.NewContextOptions harOptions = convertType(options, Browser.NewContextOptions.class);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
JsonObject recordHar = null;
Path recordHarPath = options.recordHarPath;
HarContentPolicy harContentPolicy = null;
if (options.recordHarPath != null) {
recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarContent != null) {
harContentPolicy = options.recordHarContent;
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
harContentPolicy = HarContentPolicy.OMIT;
}
if (harContentPolicy != null) {
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
}
if (options.recordHarMode != null) {
recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
}
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
options.recordHarPath = null;
options.recordHarMode = null;
options.recordHarOmitContent = null;
options.recordHarContent = null;
options.recordHarUrlFilter = null;
} else {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
@@ -154,6 +202,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
userDataDir = cwd.resolve(userDataDir);
}
params.addProperty("userDataDir", userDataDir.toString());
if (recordHar != null) {
params.add("recordHar", recordHar);
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -181,13 +232,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
params.add("selectorEngines", gson().toJsonTree(playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", playwright.selectors.testIdAttributeName);
JsonObject json = sendMessage("launchPersistentContext", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, options.tracesDir);
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.initializeHarFromOptions(harOptions);
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.tracing().setTracesDir(options.tracesDir);
return context;
}
@@ -35,8 +35,7 @@ class ChannelOwner extends LoggingSupport {
final String guid;
final JsonObject initializer;
private boolean wasCollected;
static Double NO_TIMEOUT = null;
private boolean isInternalType;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -60,6 +59,10 @@ class ChannelOwner extends LoggingSupport {
}
}
void markAsInternalType() {
isInternalType = true;
}
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
@@ -84,20 +87,16 @@ class ChannelOwner extends LoggingSupport {
return new WaitForEventLogger<>(this, apiName, code).get();
}
void withTitle(String title, Runnable code) {
withTitle(title, () -> {
code.run();
return null;
});
}
<T> T withTitle(String title, Supplier<T> code) {
String previousTitle = connection.setTitle(title);
@Override
<T> T withLogging(String apiName, Supplier<T> code) {
if (isInternalType) {
apiName = null;
}
String previousApiName = connection.setApiName(apiName);
try {
return code.get();
return super.withLogging(apiName, code);
} finally {
connection.setTitle(previousTitle);
connection.setApiName(previousApiName);
}
}
@@ -111,16 +110,11 @@ class ChannelOwner extends LoggingSupport {
}
JsonElement sendMessage(String method) {
return sendMessage(method, new JsonObject(), NO_TIMEOUT);
return sendMessage(method, new JsonObject());
}
JsonElement sendMessage(String method, JsonObject params, Double timeout) {
JsonElement sendMessage(String method, JsonObject params) {
checkNotCollected();
if (timeout != null) {
params.addProperty("timeout", timeout);
} else if (params.has("timeout")) {
throw new PlaywrightException("Internal error: timeout must be passed explicitly.");
}
return connection.sendMessage(guid, method, params);
}
@@ -5,8 +5,6 @@ import com.microsoft.playwright.Clock;
import java.util.Date;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class ClockImpl implements Clock {
private final ChannelOwner browserContext;
@@ -16,7 +14,8 @@ class ClockImpl implements Clock {
private void sendMessageWithLogging(String method, JsonObject params) {
String capitalizedMethod = method.substring(0, 1).toUpperCase() + method.substring(1);
browserContext.sendMessage("clock" + capitalizedMethod, params, NO_TIMEOUT);
browserContext.withLogging("Clock." + method,
() -> browserContext.sendMessage("clock" + capitalizedMethod, params));
}
@Override
@@ -19,6 +19,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
@@ -63,8 +64,7 @@ public class Connection {
private int lastId = 0;
private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String title;
private boolean titleReported = false;
private String apiName;
private static final boolean isLogging;
static {
String debug = System.getenv("DEBUG");
@@ -83,7 +83,7 @@ public class Connection {
PlaywrightImpl initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject(), NO_TIMEOUT);
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
}
}
@@ -116,10 +116,9 @@ public class Connection {
}
}
String setTitle(String newTitle) {
String previous = title;
titleReported = false;
title = newTitle;
String setApiName(String name) {
String previous = apiName;
apiName = name;
return previous;
}
@@ -147,14 +146,12 @@ public class Connection {
JsonObject metadata = new JsonObject();
metadata.addProperty("wallTime", currentTimeMillis());
JsonArray stack = null;
if (titleReported) {
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
if (title != null) {
metadata.addProperty("title", title);
// All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
titleReported = true;
}
metadata.addProperty("apiName", apiName);
// All but first message in an API call are considered internal and will be hidden from the inspector.
apiName = null;
if (stackTraceCollector != null) {
stack = stackTraceCollector.currentStackTrace();
if (!stack.isEmpty()) {
@@ -376,6 +373,9 @@ public class Connection {
case "Stream":
result = new Stream(parent, type, guid, initializer);
break;
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "SocksSupport":
break;
case "Tracing":
@@ -20,10 +20,13 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
@@ -34,16 +34,18 @@ class DialogImpl extends ChannelOwner implements Dialog {
@Override
public void accept(String promptText) {
JsonObject params = new JsonObject();
if (promptText != null) {
params.addProperty("promptText", promptText);
}
sendMessage("accept", params, NO_TIMEOUT);
withLogging("Dialog.accept", () -> {
JsonObject params = new JsonObject();
if (promptText != null) {
params.addProperty("promptText", promptText);
}
sendMessage("accept", params);
});
}
@Override
public void dismiss() {
sendMessage("dismiss");
withLogging("Dialog.dismiss", () -> sendMessage("dismiss"));
}
@Override
@@ -46,22 +46,22 @@ class DownloadImpl implements Download {
@Override
public void cancel() {
artifact.cancel();
page.withLogging("Download.cancel", () -> artifact.cancel());
}
@Override
public InputStream createReadStream() {
return artifact.createReadStream();
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
}
@Override
public void delete() {
artifact.delete();
page.withLogging("Download.delete", () -> artifact.delete());
}
@Override
public String failure() {
return artifact.failure();
return page.withLogging("Download.failure", () -> artifact.failure());
}
@Override
@@ -71,11 +71,11 @@ class DownloadImpl implements Download {
@Override
public Path path() {
return artifact.pathAfterFinished();
return page.withLogging("Download.path", () -> artifact.pathAfterFinished());
}
@Override
public void saveAs(Path path) {
artifact.saveAs(path);
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
}
}
@@ -40,11 +40,8 @@ import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
private final FrameImpl frame;
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.frame = (FrameImpl)parent;
}
@Override
@@ -54,83 +51,105 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public ElementHandle querySelector(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
return withLogging("ElementHandle.querySelector", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
});
}
@Override
public List<ElementHandle> querySelectorAll(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
return withLogging("ElementHandle.<", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
});
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("ElementHandle.evalOnSelector", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("ElementHandle.evalOnSelectorAll", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
public BoundingBox boundingBox() {
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
return withLogging("ElementHandle.boundingBox", () -> {
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
});
}
@Override
public void check(CheckOptions options) {
withLogging("ElementHandle.check", () -> checkImpl(options));
}
private void checkImpl(CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("check", params, frame.timeout(options.timeout));
sendMessage("check", params);
}
@Override
public void click(ClickOptions options) {
withLogging("ElementHandle.click", () -> clickImpl(options));
}
private void clickImpl(ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("click", params, frame.timeout(options.timeout));
sendMessage("click", params);
}
@Override
public Frame contentFrame() {
return withLogging("ElementHandle.contentFrame", () -> contentFrameImpl());
}
private Frame contentFrameImpl() {
JsonObject json = sendMessage("contentFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
@@ -140,132 +159,177 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void dblclick(DblclickOptions options) {
withLogging("ElementHandle.dblclick", () -> dblclickImpl(options));
}
private void dblclickImpl(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dblclick", params, frame.timeout(options.timeout));
sendMessage("dblclick", params);
}
@Override
public void dispatchEvent(String type, Object eventInit) {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params, NO_TIMEOUT);
withLogging("ElementHandle.dispatchEvent", () -> {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params);
});
}
@Override
public void fill(String value, FillOptions options) {
withLogging("ElementHandle.fill", () -> fillImpl(value, options));
}
private void fillImpl(String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("value", value);
sendMessage("fill", params, frame.timeout(options.timeout));
sendMessage("fill", params);
}
@Override
public void focus() {
sendMessage("focus");
withLogging("ElementHandle.focus", () -> sendMessage("focus"));
}
@Override
public String getAttribute(String name) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params, NO_TIMEOUT).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
return withLogging("ElementHandle.getAttribute", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
}
@Override
public void hover(HoverOptions options) {
withLogging("ElementHandle.hover", () -> hoverImpl(options));
}
private void hoverImpl(HoverOptions options) {
if (options == null) {
options = new HoverOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("hover", params, frame.timeout(options.timeout));
sendMessage("hover", params);
}
@Override
public String innerHTML() {
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
return withLogging("ElementHandle.innerHTML", () -> {
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
});
}
@Override
public String innerText() {
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
return withLogging("ElementHandle.innerText", () -> {
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
});
}
@Override
public String inputValue(InputValueOptions options) {
return withLogging("ElementHandle.inputValue", () -> inputValueImpl(options));
}
private String inputValueImpl(InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked() {
JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isChecked", () -> {
JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isDisabled() {
JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isDisabled", () -> {
JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isEditable() {
JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isEditable", () -> {
JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isEnabled() {
JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isEnabled", () -> {
JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isHidden() {
JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isHidden", () -> {
JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public boolean isVisible() {
JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean();
return withLogging("ElementHandle.isVisible", () -> {
JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean();
});
}
@Override
public FrameImpl ownerFrame() {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
return withLogging("ElementHandle.ownerFrame", () -> {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
});
}
@Override
public void press(String key, PressOptions options) {
withLogging("ElementHandle.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
sendMessage("press", params, frame.timeout(options.timeout));
sendMessage("press", params);
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
}
private byte[] screenshotImpl(ScreenshotOptions options) {
if (options == null) {
options = new ScreenshotOptions();
}
@@ -284,7 +348,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonObject json = sendMessage("screenshot", params, frame.timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
if (options.path != null) {
@@ -295,11 +359,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params, frame.timeout(options.timeout));
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
}
@Override
@@ -323,7 +383,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
@Override
@@ -332,6 +392,13 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
return selectOption(values, options);
}
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params);
}
@Override
public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) {
@@ -342,7 +409,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("options", gson().toJsonTree(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
@Override
@@ -354,21 +421,19 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("elements", Serialization.toProtocol(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
private List<String> selectOption(JsonObject params, Double timeout) {
JsonObject json = sendMessage("selectOption", params, frame.timeout(timeout)).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
private List<String> selectOption(JsonObject params) {
return withLogging("SelectOption", () -> {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
});
}
@Override
public void selectText(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params, frame.timeout(options.timeout));
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
}
@Override
@@ -385,9 +450,20 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
setInputFiles(new Path[]{files}, options);
}
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params);
}
@Override
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
FrameImpl frame = ownerFrame();
if (frame == null) {
throw new Error("Cannot set input files to detached element");
@@ -397,7 +473,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, frame.page().context());
sendMessage("setInputFiles", params, frame.timeout(options.timeout));
sendMessage("setInputFiles", params);
}
@Override
@@ -407,51 +483,77 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("payloads", Serialization.toJsonArray(files));
sendMessage("setInputFiles", params, frame.timeout(options.timeout));
sendMessage("setInputFiles", params);
}
@Override
public void tap(TapOptions options) {
withLogging("ElementHandle.tap", () -> tapImpl(options));
}
private void tapImpl(TapOptions options) {
if (options == null) {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("tap", params, frame.timeout(options.timeout));
sendMessage("tap", params);
}
@Override
public String textContent() {
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
return withLogging("ElementHandle.textContent", () -> textContentImpl());
}
private String textContentImpl() {
return withLogging("ElementHandle.textContent", () -> {
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
}
@Override
public void type(String text, TypeOptions options) {
withLogging("ElementHandle.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
sendMessage("type", params, frame.timeout(options.timeout));
sendMessage("type", params);
}
@Override
public void uncheck(UncheckOptions options) {
withLogging("ElementHandle.uncheck", () -> uncheckImpl(options));
}
private void uncheckImpl(UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("uncheck", params, frame.timeout(options.timeout));
sendMessage("uncheck", params);
}
@Override
public void waitForElementState(ElementState state, WaitForElementStateOptions options) {
withLogging("ElementHandle.waitForElementState", () -> waitForElementStateImpl(state, options));
}
private void waitForElementStateImpl(ElementState state, WaitForElementStateOptions options) {
if (options == null) {
options = new WaitForElementStateOptions();
}
@@ -460,7 +562,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
sendMessage("waitForElementState", params, frame.timeout(options.timeout));
sendMessage("waitForElementState", params);
}
private static String toProtocol(ElementState state) {
@@ -469,12 +571,16 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("ElementHandle.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
private ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
if (options == null) {
options = new WaitForSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("waitForSelector", params, frame.timeout(options.timeout)).getAsJsonObject();
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -58,7 +58,8 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(Path[] files, SetFilesOptions options) {
element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
}
@Override
@@ -68,6 +69,7 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -74,12 +74,16 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
}
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
if (options == null) {
options = new QuerySelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -89,18 +93,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<ElementHandle> querySelectorAll(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
}
@Override
@@ -117,7 +110,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
return selectOptionImpl(selector, values, options);
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
@Override
@@ -126,10 +119,24 @@ public class FrameImpl extends ChannelOwner implements Frame {
return selectOption(selector, values, options);
}
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return evalOnSelectorImpl(selector, pageFunction, arg, options);
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options));
}
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
@@ -140,14 +147,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return evalOnSelectorAllImpl(selector, pageFunction, arg);
return withLogging("Frame.evalOnSelectorAll", () -> evalOnSelectorAllImpl(selector, pageFunction, arg));
}
Object evalOnSelectorAllImpl(String selector, String pageFunction, Object arg) {
@@ -155,14 +162,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options){
return addScriptTagImpl(options);
return withLogging("Frame.addScriptTag", () -> addScriptTagImpl(options));
}
ElementHandle addScriptTagImpl(AddScriptTagOptions options) {
@@ -182,13 +189,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions, NO_TIMEOUT);
JsonElement json = sendMessage("addScriptTag", jsonOptions);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public ElementHandle addStyleTag(AddStyleTagOptions options){
return addStyleTagImpl(options);
return withLogging("Frame.addStyleTag", () -> addStyleTagImpl(options));
}
ElementHandle addStyleTagImpl(AddStyleTagOptions options) {
@@ -208,18 +215,22 @@ public class FrameImpl extends ChannelOwner implements Frame {
content += "/*# sourceURL=" + options.path.toString().replace("\n", "") + "*/";
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addStyleTag", jsonOptions, NO_TIMEOUT);
JsonElement json = sendMessage("addStyleTag", jsonOptions);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public void check(String selector, CheckOptions options){
withLogging("Frame.check", () -> checkImpl(selector, options));
}
void checkImpl(String selector, CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("check", params, timeout(options.timeout));
sendMessage("check", params);
}
@Override
@@ -229,7 +240,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void click(String selector, ClickOptions options) {
clickImpl(selector, options);
withLogging("Frame.click", () -> clickImpl(selector, options));
}
void clickImpl(String selector, ClickOptions options) {
@@ -238,26 +249,38 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("click", params, timeout(options.timeout));
sendMessage("click", params);
}
@Override
public String content() {
return withLogging("Frame.content", () -> contentImpl());
}
String contentImpl() {
return sendMessage("content").getAsJsonObject().get("value").getAsString();
}
@Override
public void dblclick(String selector, DblclickOptions options) {
withLogging("Frame.dblclick", () -> dblclickImpl(selector, options));
}
void dblclickImpl(String selector, DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("dblclick", params, timeout(options.timeout));
sendMessage("dblclick", params);
}
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Frame.dispatchEvent", () -> dispatchEventImpl(selector, type, eventInit, options));
}
void dispatchEventImpl(String selector, String type, Object eventInit, DispatchEventOptions options) {
if (options == null) {
options = new DispatchEventOptions();
}
@@ -265,55 +288,71 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params, timeout(options.timeout));
sendMessage("dispatchEvent", params);
}
@Override
public Object evaluate(String expression, Object arg) {
return withLogging("Frame.evaluate", () -> evaluateImpl(expression, arg));
}
Object evaluateImpl(String expression, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Frame.evaluateHandle", () -> evaluateHandleImpl(pageFunction, arg));
}
JSHandle evaluateHandleImpl(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
}
@Override
public void fill(String selector, String value, FillOptions options) {
withLogging("Frame.fill", () -> fillImpl(selector, value, options));
}
void fillImpl(String selector, String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("value", value);
sendMessage("fill", params, timeout(options.timeout));
sendMessage("fill", params);
}
@Override
public void focus(String selector, FocusOptions options) {
withLogging("Frame.focus", () -> focusImpl(selector, options));
}
void focusImpl(String selector, FocusOptions options) {
if (options == null) {
options = new FocusOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("focus", params, timeout(options.timeout));
sendMessage("focus", params);
}
@Override
public ElementHandle frameElement() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
return withLogging("Frame.frameElement", () -> frameElementImpl());
}
@Override
@@ -321,9 +360,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
return new FrameLocatorImpl(this, selector);
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
}
@Override
public String getAttribute(String selector, String name, GetAttributeOptions options) {
return getAttributeImpl(selector, name, options);
return withLogging("Frame.getAttribute", () -> getAttributeImpl(selector, name, options));
}
@Override
@@ -398,7 +442,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
if (json.has("value")) {
return json.get("value").getAsString();
}
@@ -407,7 +451,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return navigateImpl(url, options);
return withLogging("Page.navigate", () -> navigateImpl(url, options));
}
ResponseImpl navigateImpl(String url, NavigateOptions options) {
@@ -416,7 +460,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("url", url);
JsonElement result = sendMessage("goto", params, navigationTimeout(options.timeout));
JsonElement result = sendMessage("goto", params);
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) {
return null;
@@ -426,7 +470,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void hover(String selector, HoverOptions options) {
hoverImpl(selector, options);
withLogging("Frame.hover", () -> hoverImpl(selector, options));
}
void hoverImpl(String selector, HoverOptions options) {
@@ -435,12 +479,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("hover", params, timeout(options.timeout));
sendMessage("hover", params);
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
dragAndDropImpl(source, target, options);
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options));
}
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
@@ -450,12 +494,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source);
params.addProperty("target", target);
sendMessage("dragAndDrop", params, timeout(options.timeout));
sendMessage("dragAndDrop", params);
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return innerHTMLImpl(selector, options);
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
}
String innerHTMLImpl(String selector, InnerHTMLOptions options) {
@@ -464,13 +508,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("innerHTML", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("innerHTML", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String innerText(String selector, InnerTextOptions options) {
return innerTextImpl(selector, options);
return withLogging("Frame.innerText", () -> innerTextImpl(selector, options));
}
String innerTextImpl(String selector, InnerTextOptions options) {
@@ -479,13 +523,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("innerText", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("innerText", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return inputValueImpl(selector, options);
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options));
}
String inputValueImpl(String selector, InputValueOptions options) {
@@ -494,13 +538,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("inputValue", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return isCheckedImpl(selector, options);
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
}
boolean isCheckedImpl(String selector, IsCheckedOptions options) {
@@ -509,7 +553,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isChecked", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isChecked", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@@ -520,7 +564,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return isDisabledImpl(selector, options);
return withLogging("Page.isDisabled", () -> isDisabledImpl(selector, options));
}
boolean isDisabledImpl(String selector, IsDisabledOptions options) {
@@ -529,13 +573,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isDisabled", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isDisabled", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return isEditableImpl(selector, options);
return withLogging("Page.isEditable", () -> isEditableImpl(selector, options));
}
boolean isEditableImpl(String selector, IsEditableOptions options) {
@@ -544,13 +588,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEditable", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isEditable", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return isEnabledImpl(selector, options);
return withLogging("Page.isEnabled", () -> isEnabledImpl(selector, options));
}
boolean isEnabledImpl(String selector, IsEnabledOptions options) {
@@ -559,13 +603,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEnabled", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isEnabled", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return isHiddenImpl(selector, options);
return withLogging("Page.isHidden", () -> isHiddenImpl(selector, options));
}
boolean isHiddenImpl(String selector, IsHiddenOptions options) {
@@ -574,13 +618,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isHidden", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isHidden", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return isVisibleImpl(selector, options);
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
}
@Override
@@ -594,7 +638,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isVisible", params, timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("isVisible", params).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@@ -615,7 +659,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void press(String selector, String key, PressOptions options) {
pressImpl(selector, key, options);
withLogging("Frame.press", () -> pressImpl(selector, key, options));
}
void pressImpl(String selector, String key, PressOptions options) {
@@ -625,12 +669,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("key", key);
sendMessage("press", params, timeout(options.timeout));
sendMessage("press", params);
}
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return selectOptionImpl(selector, values, options);
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
@@ -642,7 +686,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("options", gson().toJsonTree(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
List<String> selectOptionImpl(String selector, String[] values, SelectOptionOptions options) {
@@ -654,12 +698,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return selectOptionImpl(selector, values, options);
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, ElementHandle[] values, SelectOptionOptions options) {
@@ -671,35 +715,30 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("elements", Serialization.toProtocol(values));
}
return selectOption(params, options.timeout);
return selectOption(params);
}
private List<String> selectOption(JsonObject params, Double timeout) {
JsonObject json = sendMessage("selectOption", params, timeout(timeout)).getAsJsonObject();
private List<String> selectOption(JsonObject params) {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
setCheckedImpl(selector, checked, options);
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options));
}
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
check(selector, convertType(options, CheckOptions.class));
checkImpl(selector, convertType(options, CheckOptions.class));
} else {
uncheck(selector, convertType(options, UncheckOptions.class));
uncheckImpl(selector, convertType(options, UncheckOptions.class));
}
}
@Override
public void setContent(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
sendMessage("setContent", params, navigationTimeout(options.timeout));
withLogging("Frame.setContent", () -> setContentImpl(html, options));
}
@Override
@@ -707,9 +746,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
setInputFiles(selector, new Path[] {files}, options);
}
void setContentImpl(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
sendMessage("setContent", params);
}
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
setInputFilesImpl(selector, files, options);
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
@@ -719,7 +767,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFiles", params, timeout(options.timeout));
sendMessage("setInputFiles", params);
}
@Override
@@ -729,7 +777,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
setInputFilesImpl(selector, files, options);
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
@@ -740,54 +788,73 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.add("payloads", toJsonArray(files));
sendMessage("setInputFiles", params, timeout(options.timeout));
sendMessage("setInputFiles", params);
}
@Override
public void tap(String selector, TapOptions options) {
withLogging("Frame.tap", () -> tapImpl(selector, options));
}
void tapImpl(String selector, TapOptions options) {
if (options == null) {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("tap", params, timeout(options.timeout));
sendMessage("tap", params);
}
@Override
public String textContent(String selector, TextContentOptions options) {
return withLogging("Frame.textContent", () -> textContentImpl(selector, options));
}
String textContentImpl(String selector, TextContentOptions options) {
if (options == null) {
options = new TextContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
return sendMessage("textContent", params, timeout(options.timeout)).getAsJsonObject().get("value").getAsString();
return sendMessage("textContent", params).getAsJsonObject().get("value").getAsString();
}
@Override
public String title() {
return withLogging("Frame.title", () -> titleImpl());
}
String titleImpl() {
JsonElement json = sendMessage("title");
return json.getAsJsonObject().get("value").getAsString();
}
@Override
public void type(String selector, String text, TypeOptions options) {
withLogging("Frame.type", () -> typeImpl(selector, text, options));
}
void typeImpl(String selector, String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("text", text);
sendMessage("type", params, timeout(options.timeout));
sendMessage("type", params);
}
@Override
public void uncheck(String selector, UncheckOptions options) {
withLogging("Frame.uncheck", () -> uncheckImpl(selector, options));
}
void uncheckImpl(String selector, UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("uncheck", params, timeout(options.timeout));
sendMessage("uncheck", params);
}
@Override
@@ -797,13 +864,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Frame.waitForFunction", () -> waitForFunctionImpl(pageFunction, arg, options));
}
JSHandle waitForFunctionImpl(String pageFunction, Object arg, WaitForFunctionOptions options) {
if (options == null) {
options = new WaitForFunctionOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("waitForFunction", params, timeout(options.timeout));
JsonElement json = sendMessage("waitForFunction", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
return connection.getExistingObject(element.get("guid").getAsString());
}
@@ -960,7 +1031,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl(), options.url, this.connection.localUtils, false);
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
logger.log("waiting for navigation " + matcher);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
@@ -972,6 +1043,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Frame.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
return waitForSelectorImpl(selector, options, false);
}
@@ -982,7 +1057,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("omitReturnValue", omitReturnValue);
JsonElement json = sendMessage("waitForSelector", params, timeout(options.timeout));
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -992,14 +1067,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForTimeout(double timeout) {
withLogging("Frame.waitForTimeout", () -> waitForTimeoutImpl(timeout));
}
void waitForTimeoutImpl(double timeout) {
JsonObject params = new JsonObject();
params.addProperty("waitTimeout", timeout);
sendMessage("waitForTimeout", params, NO_TIMEOUT);
params.addProperty("timeout", timeout);
sendMessage("waitForTimeout", params);
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(UrlMatcher.forGlob(page.context().baseUrl(), url, this.connection.localUtils, false), options);
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
}
@Override
@@ -1034,14 +1113,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
int queryCount(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = sendMessage("queryCount", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
return result.get("value").getAsInt();
}
void highlightImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
sendMessage("highlight", params, NO_TIMEOUT);
sendMessage("highlight", params);
}
protected void handleEvent(String event, JsonObject params) {
@@ -1072,30 +1151,4 @@ public class FrameImpl extends ChannelOwner implements Frame {
internalListeners.notify(InternalEventType.NAVIGATED, params);
}
}
protected double timeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.timeout(timeout);
}
return new TimeoutSettings().timeout(timeout);
}
protected double navigationTimeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.navigationTimeout(timeout);
}
return new TimeoutSettings().navigationTimeout(timeout);
}
FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
return withTitle(title, () -> expect(expression, options));
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", expression);
JsonElement json = sendMessage("expect", params, options.timeout);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
}
@@ -26,7 +26,6 @@ import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.LoggingSupport.*;
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -42,7 +41,7 @@ public class HARRouter {
JsonObject params = new JsonObject();
params.addProperty("file", harFile.toString());
JsonObject json = localUtils.sendMessage("harOpen", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject();
if (json.has("error")) {
throw new PlaywrightException(json.get("error").getAsString());
}
@@ -62,7 +61,7 @@ public class HARRouter {
params.addProperty("postData", base64);
}
params.addProperty("isNavigationRequest", request.isNavigationRequest());
JsonObject response = localUtils.sendMessage("harLookup", params, NO_TIMEOUT).getAsJsonObject();
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject();
String action = response.get("action").getAsString();
if ("redirect".equals(action)) {
@@ -41,58 +41,70 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
@Override
public void dispose() {
try {
sendMessage("dispose");
} catch (TargetClosedError e) {
}
withLogging("JSHandle.dispose", () -> {
try {
sendMessage("dispose");
} catch (TargetClosedError e) {
}
});
}
@Override
public Object evaluate(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("JSHandle.evaluate", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
return withLogging("JSHandle.evaluateHandle", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
});
}
@Override
public Map<String, JSHandle> getProperties() {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
return withLogging("JSHandle.getProperties", () -> {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
});
}
@Override
public JSHandle getProperty(String propertyName) {
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
return withLogging("JSHandle.getProperty", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
});
}
@Override
public Object jsonValue() {
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("JSHandle.jsonValue", () -> {
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
@@ -44,7 +44,7 @@ class JsonPipe extends ChannelOwner implements Transport {
checkIfClosed();
JsonObject params = new JsonObject();
params.add("message", message);
sendMessage("send", params, NO_TIMEOUT);
sendMessage("send", params);
}
@Override
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Keyboard;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl implements Keyboard {
@@ -31,21 +30,25 @@ class KeyboardImpl implements Keyboard {
@Override
public void down(String key) {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params, NO_TIMEOUT);
page.withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
});
}
@Override
public void insertText(String text) {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params, NO_TIMEOUT);
page.withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
});
}
@Override
public void press(String key, PressOptions options) {
pressImpl(key, options);
page.withLogging("Keyboard.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
@@ -54,12 +57,12 @@ class KeyboardImpl implements Keyboard {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardPress", params, NO_TIMEOUT);
page.sendMessage("keyboardPress", params);
}
@Override
public void type(String text, TypeOptions options) {
typeImpl(text, options);
page.withLogging("Keyboard.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
@@ -68,13 +71,15 @@ class KeyboardImpl implements Keyboard {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardType", params, NO_TIMEOUT);
page.sendMessage("keyboardType", params);
}
@Override
public void up(String key) {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params, NO_TIMEOUT);
page.withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
});
}
}
@@ -21,13 +21,13 @@ import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
public class LocalUtils extends ChannelOwner {
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
JsonArray deviceDescriptors() {
@@ -41,13 +41,13 @@ public class LocalUtils extends ChannelOwner {
params.addProperty("mode", appendMode ? "append" : "write");
params.addProperty("stacksId", stacksId);
params.addProperty("includeSources", includeSources);
sendMessage("zip", params, NO_TIMEOUT);
sendMessage("zip", params);
}
void traceDiscarded(String stacksId) {
JsonObject params = new JsonObject();
params.addProperty("stacksId", stacksId);
sendMessage("traceDiscarded", params, NO_TIMEOUT);
sendMessage("traceDiscarded", params);
}
String tracingStarted(String tracesDir, String traceName) {
@@ -56,19 +56,7 @@ public class LocalUtils extends ChannelOwner {
params.addProperty("tracesDir", "");
}
params.addProperty("traceName", traceName);
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params).getAsJsonObject();
return json.get("stacksId").getAsString();
}
public Pattern globToRegex(String glob, String baseURL, boolean webSocketUrl) {
JsonObject params = new JsonObject();
params.addProperty("glob", glob);
if (baseURL != null) {
params.addProperty("baseURL", baseURL);
}
params.addProperty("webSocketUrl", webSocketUrl);
JsonObject json = connection.localUtils().sendMessage("globToRegex", params, NO_TIMEOUT).getAsJsonObject();
String regex = json.get("regex").getAsString();
return Pattern.compile(regex);
}
}
@@ -20,6 +20,7 @@ import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.options.AriaRole;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -30,39 +31,12 @@ import static com.microsoft.playwright.impl.Serialization.serializeArgument;
import static com.microsoft.playwright.impl.Utils.convertType;
public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions {
LocatorImpl actualLocator;
public LocatorAssertionsImpl(Locator locator) {
this(locator, false);
}
private LocatorAssertionsImpl(Locator locator, boolean isNot) {
super(isNot);
this.actualLocator = (LocatorImpl) locator;
}
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
return actualLocator.expect(expression, expectOptions, title);
}
@Override
public void containsClass(String classname, ContainsClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = classname;
expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
}
@Override
public void containsClass(List<String> classnames, ContainsClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : classnames) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
super((LocatorImpl) locator, isNot);
}
@Override
@@ -72,7 +46,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -81,7 +55,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -95,7 +69,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -108,7 +82,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -117,7 +91,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = description;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -125,7 +99,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -134,7 +108,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = errorMessage;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -142,7 +116,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -151,7 +125,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = name;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -159,7 +133,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -187,20 +161,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Assert \"hasAttribute\"");
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasClass(String text, HasClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(Pattern pattern, HasClassOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -211,7 +185,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text;
list.add(expected);
}
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -221,7 +195,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = expectedRegex(pattern);
list.add(expected);
}
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -232,7 +206,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = (double) count;
List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Assert \"hasCount\"");
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
}
@Override
@@ -258,20 +232,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Assert \"hasCSS\"");
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasId(String id, HasIdOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = id;
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasId(Pattern pattern, HasIdOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -283,14 +257,14 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
commonOptions.expressionArg = name;
commonOptions.expectedValue = serializeArgument(value);
List<ExpectedTextValue> list = null;
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Assert \"hasJSProperty\"");
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
}
@Override
public void hasRole(AriaRole role, HasRoleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = role.toString().toLowerCase();
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Assert \"hasRole\"");
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -300,7 +274,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -310,7 +284,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
// Just match substring, same as containsText.
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -324,7 +298,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -337,20 +311,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(String value, HasValueOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value;
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(Pattern pattern, HasValueOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -361,7 +335,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text;
list.add(expected);
}
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -372,7 +346,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.matchSubstring = true;
list.add(expected);
}
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -382,7 +356,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
}
FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
options.expectedValue = serializeArgument(expected);
expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot");
}
@Override
@@ -410,12 +384,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
String message = "Locator expected to be";
FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
expectOptions.expectedValue = serializeArgument(expectedValue);
expectImpl("to.be.checked", expectOptions, expected, message, "Assert \"isChecked\"");
expectImpl("to.be.checked", expectOptions, expected, message);
}
@Override
public void isDisabled(IsDisabledOptions options) {
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Assert \"isDisabled\"");
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -423,12 +397,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean editable = options == null || options.editable == null || options.editable == true;
String message = "Locator expected to be " + (editable ? "editable" : "readonly");
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Assert \"isEditable\"");
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions);
}
@Override
public void isEmpty(IsEmptyOptions options) {
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Assert \"isEmpty\"");
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -436,17 +410,17 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean enabled = options == null || options.enabled == null || options.enabled == true;
String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Assert \"isEnabled\"");
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions);
}
@Override
public void isFocused(IsFocusedOptions options) {
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Assert \"isFocused\"");
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
}
@Override
public void isHidden(IsHiddenOptions options) {
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Assert \"isHidden\"");
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -455,7 +429,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (options != null && options.ratio != null) {
expectOptions.expectedNumber = options.ratio;
}
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions, "Assert \"isInViewport\"");
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions);
}
@Override
@@ -463,12 +437,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean visible = options == null || options.visible == null || options.visible == true;
String message = "Locator expected to be " + (visible ? "visible" : "hidden");
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Assert \"isVisible\"");
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions);
}
private void expectTrue(String expression, String message, FrameExpectOptions options, String title) {
private void expectTrue(String expression, String message, FrameExpectOptions options) {
List<ExpectedTextValue> expectedText = null;
expectImpl(expression, expectedText, null, message, options, title);
expectImpl(expression, expectedText, null, message, options);
}
@Override
@@ -481,6 +455,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean attached = options == null || options.attached == null || options.attached == true;
String message = "Locator expected to be " + (attached ? "attached" : "detached");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Assert \"isAttached\"");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions);
}
}
@@ -67,25 +67,27 @@ class LocatorImpl implements Locator {
this.selector = selector;
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options, String title) {
return frame.withTitle(title, () -> {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
// timeout = handleOptions.timeout;
// }
// timeout = frame.page.timeoutSettings.timeout(timeout);
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
ElementHandle handle = elementHandle(handleOptions);
try {
return callback.apply(handle, options);
} finally {
if (handle != null) {
handle.dispose();
}
private static String escapeWithQuotes(String text) {
return gson().toJson(text);
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
// timeout = handleOptions.timeout;
// }
// timeout = frame.page.timeoutSettings.timeout(timeout);
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
ElementHandle handle = elementHandle(handleOptions);
try {
return callback.apply(handle, options);
} finally {
if (handle != null) {
handle.dispose();
}
});
}
}
@Override
@@ -118,29 +120,37 @@ class LocatorImpl implements Locator {
@Override
public String ariaSnapshot(AriaSnapshotOptions options) {
return frame.withLogging("Locator.ariaSnapshot", () -> ariaSnapshotImpl(options));
}
private String ariaSnapshotImpl(AriaSnapshotOptions options) {
if (options == null) {
options = new AriaSnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("ariaSnapshot", params, frame.timeout(options.timeout)).getAsJsonObject();
JsonObject result = frame.sendMessage("ariaSnapshot", params).getAsJsonObject();
return result.get("snapshot").getAsString();
}
@Override
public void blur(BlurOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options));
}
private void blurImpl(BlurOptions options) {
if (options == null) {
options = new BlurOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("strict", true);
frame.sendMessage("blur", params, frame.timeout(options.timeout));
frame.sendMessage("blur", params);
}
@Override
public BoundingBox boundingBox(BoundingBoxOptions options) {
return withElement((h, o) -> h.boundingBox(), options, "Bounding Box");
return withElement((h, o) -> h.boundingBox(), options);
}
@Override
@@ -153,7 +163,7 @@ class LocatorImpl implements Locator {
@Override
public void clear(ClearOptions options) {
frame.withTitle("Clear", () -> fill("", convertType(options, FillOptions.class)));
fill("", convertType(options, FillOptions.class));
}
@Override
@@ -169,11 +179,6 @@ class LocatorImpl implements Locator {
return frame.queryCount(selector);
}
@Override
public Locator describe(String description) {
return locator(describeSelector(description));
}
@Override
public void dblclick(DblclickOptions options) {
if (options == null) {
@@ -223,7 +228,7 @@ class LocatorImpl implements Locator {
@Override
public Object evaluate(String expression, Object arg, EvaluateOptions options) {
return withElement((h, o) -> h.evaluate(expression, arg), options, "Evaluate");
return withElement((h, o) -> h.evaluate(expression, arg), options);
}
@Override
@@ -233,7 +238,7 @@ class LocatorImpl implements Locator {
@Override
public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
return withElement((h, o) -> h.evaluateHandle(expression, arg), options, "Evaluate");
return withElement((h, o) -> h.evaluateHandle(expression, arg), options);
}
@Override
@@ -478,7 +483,7 @@ class LocatorImpl implements Locator {
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class), "Screenshot");
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
}
@Override
@@ -486,7 +491,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o);
return null;
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class), "Scroll into view");
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}
@Override
@@ -542,7 +547,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.selectText(o);
return null;
}, convertType(options, ElementHandle.SelectTextOptions.class), "Select text");
}, convertType(options, ElementHandle.SelectTextOptions.class));
}
@Override
@@ -622,7 +627,11 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new WaitForOptions();
}
frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true);
waitForImpl(options);
}
private void waitForImpl(WaitForOptions options) {
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
}
@Override
@@ -644,9 +653,8 @@ class LocatorImpl implements Locator {
return frame.hashCode() ^ selector.hashCode();
}
FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
options.selector = selector;
return frame.expect(expression, options, title);
FrameExpectResult expect(String expression, FrameExpectOptions options) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
}
JsonObject toProtocol() {
@@ -655,4 +663,13 @@ class LocatorImpl implements Locator {
result.addProperty("selector", selector);
return result;
}
private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", expression);
JsonElement json = frame.sendMessage("expect", params);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
}
@@ -39,12 +39,9 @@ public class LocatorUtils {
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
}
static String describeSelector(String description) {
return "internal:describe=" + gson().toJson(description);
}
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName;
return getByAttributeTextSelector(testIdAttributeName, testId, true);
}
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;
class LoggingSupport {
private static final boolean isEnabled;
@@ -30,6 +31,29 @@ class LoggingSupport {
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
void withLogging(String apiName, Runnable code) {
withLogging(apiName, () -> {
code.run();
return null;
});
}
<T> T withLogging(String apiName, Supplier<T> code) {
if (isEnabled) {
logApi("=> " + apiName + " started");
}
boolean success = false;
try {
T result = code.get();
success = true;
return result;
} finally {
if (isEnabled) {
logApi("<= " + apiName + (success ? " succeeded" : " failed"));
}
}
}
static void logWithTimestamp(String message) {
// This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat);
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Mouse;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
@@ -32,18 +31,22 @@ class MouseImpl implements Mouse {
@Override
public void click(double x, double y, ClickOptions options) {
page.withLogging("Mouse.click", () -> clickImpl(x, y, options));
}
private void clickImpl(double x, double y, ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("mouseClick", params, NO_TIMEOUT);
page.sendMessage("mouseClick", params);
}
@Override
public void dblclick(double x, double y, DblclickOptions options) {
page.withTitle("Double click", () -> dblclickImpl(x, y, options));
page.withLogging("Mouse.dblclick", () -> dblclickImpl(x, y, options));
}
private void dblclickImpl(double x, double y, DblclickOptions options) {
@@ -59,38 +62,52 @@ class MouseImpl implements Mouse {
@Override
public void down(DownOptions options) {
page.withLogging("Mouse.down", () -> downImpl(options));
}
private void downImpl(DownOptions options) {
if (options == null) {
options = new DownOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseDown", params, NO_TIMEOUT);
page.sendMessage("mouseDown", params);
}
@Override
public void move(double x, double y, MoveOptions options) {
page.withLogging("Mouse.move", () -> moveImpl(x, y, options));
}
private void moveImpl(double x, double y, MoveOptions options) {
if (options == null) {
options = new MoveOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("mouseMove", params, NO_TIMEOUT);
page.sendMessage("mouseMove", params);
}
@Override
public void up(UpOptions options) {
if (options == null) {
options = new UpOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseUp", params, NO_TIMEOUT);
page.withLogging("Mouse.up", () -> upImpl(options));
}
@Override
public void wheel(double deltaX, double deltaY) {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params, NO_TIMEOUT);
page.withLogging("Mouse.wheel", () -> {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params);
});
}
private void upImpl(UpOptions options) {
if (options == null) {
options = new UpOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseUp", params);
}
}
@@ -21,6 +21,7 @@ import com.microsoft.playwright.assertions.PageAssertions;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorAssertionsImpl.shouldIgnoreCase;
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
import static com.microsoft.playwright.impl.Utils.convertType;
@@ -32,45 +33,39 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
}
private PageAssertionsImpl(Page page, boolean isNot) {
super(isNot);
super((LocatorImpl) page.locator(":root"), isNot);
this.actualPage = (PageImpl) page;
}
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
FrameImpl frame = (FrameImpl) actualPage.mainFrame();
return frame.expect(expression, expectOptions, title);
}
@Override
public void hasTitle(String title, HasTitleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = title;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasTitle(Pattern pattern, HasTitleOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasURL(String url, HasURLOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
if (actualPage.context().baseUrl() != null) {
url = resolveUrl(actualPage.context().baseUrl(), url);
if (actualPage.context().baseUrl != null) {
url = resolveUrl(actualPage.context().baseUrl, url);
}
expected.string = url;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasURL(Pattern pattern, HasURLOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class));
}
@Override
@@ -95,7 +95,7 @@ public class PageImpl extends ChannelOwner implements Page {
BrowserContextImpl ownedContext;
private boolean isClosed;
final Set<Worker> workers = new HashSet<>();
protected final TimeoutSettings timeoutSettings;
private final TimeoutSettings timeoutSettings;
private VideoImpl video;
private final PageImpl opener;
private String closeReason;
@@ -546,7 +546,7 @@ public class PageImpl extends ChannelOwner implements Page {
ownedContext.close();
} else {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params, NO_TIMEOUT);
sendMessage("close", params);
}
} catch (PlaywrightException exception) {
if (isSafeCloseError(exception) && (options.runBeforeUnload == null || !options.runBeforeUnload)) {
@@ -558,13 +558,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return mainFrame.querySelector(
selector, convertType(options, Frame.QuerySelectorOptions.class));
return withLogging("Page.querySelector", () -> mainFrame.querySelectorImpl(
selector, convertType(options, Frame.QuerySelectorOptions.class)));
}
@Override
public List<ElementHandle> querySelectorAll(String selector) {
return mainFrame.querySelectorAll(selector);
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}
@Override
@@ -580,15 +580,17 @@ public class PageImpl extends ChannelOwner implements Page {
return;
}
AddLocatorHandlerOptions finalOptions = options;
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) {
params.addProperty("noWaitAfter", true);
}
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params, NO_TIMEOUT);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times));
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) {
params.addProperty("noWaitAfter", true);
}
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times));
});
}
@Override
@@ -599,7 +601,7 @@ public class PageImpl extends ChannelOwner implements Page {
JsonObject params = new JsonObject();
params.addProperty("uid", entry.getKey());
try {
sendMessage("unregisterLocatorHandler", params, NO_TIMEOUT);
sendMessage("unregisterLocatorHandler", params);
} catch (PlaywrightException e) {
}
}
@@ -624,65 +626,71 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return mainFrame.evalOnSelectorImpl(
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class));
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
selector, pageFunction, arg, convertType(options, Frame.EvalOnSelectorOptions.class)));
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return mainFrame.evalOnSelectorAllImpl(selector, pageFunction, arg);
return withLogging("Page.evalOnSelectorAll", () -> mainFrame.evalOnSelectorAllImpl(selector, pageFunction, arg));
}
@Override
public void addInitScript(String script) {
addInitScriptImpl(script);
withLogging("Page.addInitScript", () -> addInitScriptImpl(script));
}
@Override
public void addInitScript(Path path) {
try {
byte[] bytes = readAllBytes(path);
String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
withLogging("Page.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
});
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params, NO_TIMEOUT);
sendMessage("addInitScript", params);
}
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options) {
return mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class));
return withLogging("Page.addScriptTag",
() -> mainFrame.addScriptTagImpl(convertType(options, Frame.AddScriptTagOptions.class)));
}
@Override
public ElementHandle addStyleTag(AddStyleTagOptions options) {
return mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class));
return withLogging("Page.addStyleTag",
() -> mainFrame.addStyleTagImpl(convertType(options, Frame.AddStyleTagOptions.class)));
}
@Override
public void bringToFront() {
sendMessage("bringToFront");
withLogging("Page.bringToFront", () -> sendMessage("bringToFront"));
}
@Override
public void check(String selector, CheckOptions options) {
mainFrame.check(selector, convertType(options, Frame.CheckOptions.class));
withLogging("Page.check",
() -> mainFrame.checkImpl(selector, convertType(options, Frame.CheckOptions.class)));
}
@Override
public void click(String selector, ClickOptions options) {
mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class));
withLogging("Page.click",
() -> mainFrame.clickImpl(selector, convertType(options, Frame.ClickOptions.class)));
}
@Override
public String content() {
return mainFrame.content();
return withLogging("Page.content", () -> mainFrame.contentImpl());
}
@Override
@@ -692,17 +700,19 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void dblclick(String selector, DblclickOptions options) {
mainFrame.dblclick(selector, convertType(options, Frame.DblclickOptions.class));
withLogging("Page.dblclick",
() -> mainFrame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class)));
}
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
mainFrame.dispatchEvent(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class));
withLogging("Page.dispatchEvent",
() -> mainFrame.dispatchEventImpl(selector, type, eventInit, convertType(options, Frame.DispatchEventOptions.class)));
}
@Override
public void emulateMedia(EmulateMediaOptions options) {
emulateMediaImpl(options);
withLogging("Page.emulateMedia", () -> emulateMediaImpl(options));
}
private void emulateMediaImpl(EmulateMediaOptions options) {
@@ -710,22 +720,22 @@ public class PageImpl extends ChannelOwner implements Page {
options = new EmulateMediaOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("emulateMedia", params, NO_TIMEOUT);
sendMessage("emulateMedia", params);
}
@Override
public Object evaluate(String expression, Object arg) {
return mainFrame.evaluate(expression, arg);
return withLogging("Page.evaluate", () -> mainFrame.evaluateImpl(expression, arg));
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
return mainFrame.evaluateHandle(pageFunction, arg);
return withLogging("Page.evaluateHandle", () -> mainFrame.evaluateHandleImpl(pageFunction, arg));
}
@Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
exposeBindingImpl(name, playwrightBinding, options);
withLogging("Page.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
@@ -742,22 +752,25 @@ public class PageImpl extends ChannelOwner implements Page {
if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true);
}
sendMessage("exposeBinding", params, NO_TIMEOUT);
sendMessage("exposeBinding", params);
}
@Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
withLogging("Page.exposeFunction",
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
public void fill(String selector, String value, FillOptions options) {
mainFrame.fill(selector, value, convertType(options, Frame.FillOptions.class));
withLogging("Page.fill",
() -> mainFrame.fillImpl(selector, value, convertType(options, Frame.FillOptions.class)));
}
@Override
public void focus(String selector, FocusOptions options) {
mainFrame.focus(selector, convertType(options, Frame.FocusOptions.class));
withLogging("Page.focus",
() -> mainFrame.focusImpl(selector, convertType(options, Frame.FocusOptions.class)));
}
@Override
@@ -772,7 +785,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Frame frameByUrl(String glob) {
return frameFor(UrlMatcher.forGlob(browserContext.baseUrl(), glob, this.connection.localUtils, false));
return frameFor(new UrlMatcher(browserContext.baseUrl, glob));
}
@Override
@@ -806,77 +819,89 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public String getAttribute(String selector, String name, GetAttributeOptions options) {
return mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class));
return withLogging("Page.getAttribute",
() -> mainFrame.getAttributeImpl(selector, name, convertType(options, Frame.GetAttributeOptions.class)));
}
@Override
public Locator getByAltText(String text, GetByAltTextOptions options) {
return mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class));
return withLogging("Page.getAttribute",
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
}
@Override
public Locator getByAltText(Pattern text, GetByAltTextOptions options) {
return mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class));
return withLogging("Page.getByAltText",
() -> mainFrame.getByAltText(text, convertType(options, Frame.GetByAltTextOptions.class)));
}
@Override
public Locator getByLabel(String text, GetByLabelOptions options) {
return mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class));
return withLogging("Page.getByLabel",
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
}
@Override
public Locator getByLabel(Pattern text, GetByLabelOptions options) {
return mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class));
return withLogging("Page.getByLabel",
() -> mainFrame.getByLabel(text, convertType(options, Frame.GetByLabelOptions.class)));
}
@Override
public Locator getByPlaceholder(String text, GetByPlaceholderOptions options) {
return mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class));
return withLogging("Page.getByPlaceholder",
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options) {
return mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class));
return withLogging("Page.getByPlaceholder",
() -> mainFrame.getByPlaceholder(text, convertType(options, Frame.GetByPlaceholderOptions.class)));
}
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return mainFrame.getByRole(role, convertType(options, Frame.GetByRoleOptions.class));
return withLogging("Page.getByRole",
() -> mainFrame.getByRole(role, convertType(options, Frame.GetByRoleOptions.class)));
}
@Override
public Locator getByTestId(String testId) {
return mainFrame.getByTestId(testId);
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId));
}
@Override
public Locator getByTestId(Pattern testId) {
return mainFrame.getByTestId(testId);
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class));
return withLogging("Page.getByText",
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
}
@Override
public Locator getByText(Pattern text, GetByTextOptions options) {
return mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class));
return withLogging("Page.getByText",
() -> mainFrame.getByText(text, convertType(options, Frame.GetByTextOptions.class)));
}
@Override
public Locator getByTitle(String text, GetByTitleOptions options) {
return mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class));
return withLogging("Page.getByTitle",
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
}
@Override
public Locator getByTitle(Pattern text, GetByTitleOptions options) {
return mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class));
return withLogging("Page.getByTitle",
() -> mainFrame.getByTitle(text, convertType(options, Frame.GetByTitleOptions.class)));
}
@Override
public Response goBack(GoBackOptions options) {
return goBackImpl(options);
return withLogging("Page.goBack", () -> goBackImpl(options));
}
Response goBackImpl(GoBackOptions options) {
@@ -884,7 +909,7 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoBackOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("goBack", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("goBack", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
}
@@ -893,7 +918,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Response goForward(GoForwardOptions options) {
return goForwardImpl(options);
return withLogging("Page.goForward", () -> goForwardImpl(options));
}
Response goForwardImpl(GoForwardOptions options) {
@@ -901,7 +926,7 @@ public class PageImpl extends ChannelOwner implements Page {
options = new GoForwardOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("goForward", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("goForward", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
}
@@ -910,42 +935,46 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void requestGC() {
sendMessage("requestGC");
withLogging("Page.requestGC", () -> sendMessage("requestGC"));
}
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class));
return withLogging("Page.navigate", () -> mainFrame.navigateImpl(url, convertType(options, Frame.NavigateOptions.class)));
}
@Override
public void hover(String selector, HoverOptions options) {
mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class));
withLogging("Page.hover", () -> mainFrame.hoverImpl(selector, convertType(options, Frame.HoverOptions.class)));
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class));
withLogging("Page.dragAndDrop", () -> mainFrame.dragAndDropImpl(source, target, convertType(options, Frame.DragAndDropOptions.class)));
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class));
return withLogging("Page.innerHTML",
() -> mainFrame.innerHTMLImpl(selector, convertType(options, Frame.InnerHTMLOptions.class)));
}
@Override
public String innerText(String selector, InnerTextOptions options) {
return mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class));
return withLogging("Page.innerText",
() -> mainFrame.innerTextImpl(selector, convertType(options, Frame.InnerTextOptions.class)));
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class));
return withLogging("Page.inputValue",
() -> mainFrame.inputValueImpl(selector, convertType(options, Frame.InputValueOptions.class)));
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class));
return withLogging("Page.isChecked",
() -> mainFrame.isCheckedImpl(selector, convertType(options, Frame.IsCheckedOptions.class)));
}
@Override
@@ -955,27 +984,32 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class));
return withLogging("Page.isDisabled",
() -> mainFrame.isDisabledImpl(selector, convertType(options, Frame.IsDisabledOptions.class)));
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class));
return withLogging("Page.isEditable",
() -> mainFrame.isEditableImpl(selector, convertType(options, Frame.IsEditableOptions.class)));
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class));
return withLogging("Page.isEnabled",
() -> mainFrame.isEnabledImpl(selector, convertType(options, Frame.IsEnabledOptions.class)));
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class));
return withLogging("Page.isHidden",
() -> mainFrame.isHiddenImpl(selector, convertType(options, Frame.IsHiddenOptions.class)));
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class));
return withLogging("Page.isVisible",
() -> mainFrame.isVisibleImpl(selector, convertType(options, Frame.IsVisibleOptions.class)));
}
@Override
@@ -1008,21 +1042,23 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void pause() {
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout();
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout();
browserContext.setDefaultNavigationTimeout(0.0);
browserContext.setDefaultTimeout(0.0);
try {
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
} finally {
browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
browserContext.setDefaultTimeout(defaultTimeout);
}
withLogging("Page.pause", () -> {
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout();
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout();
browserContext.setDefaultNavigationTimeoutImpl(0.0);
browserContext.setDefaultTimeoutImpl(0.0);
try {
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
} finally {
browserContext.setDefaultNavigationTimeoutImpl(defaultNavigationTimeout);
browserContext.setDefaultTimeoutImpl(defaultTimeout);
}
});
}
@Override
public byte[] pdf(PdfOptions options) {
return pdfImpl(options);
return withLogging("Page.pdf", () -> pdfImpl(options));
}
private byte[] pdfImpl(PdfOptions options) {
@@ -1031,7 +1067,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonObject json = sendMessage("pdf", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("pdf", params).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("pdf").getAsString());
if (options.path != null) {
Utils.writeToFile(buffer, options.path);
@@ -1041,12 +1077,13 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void press(String selector, String key, PressOptions options) {
mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class));
withLogging("Page.press",
() -> mainFrame.pressImpl(selector, key, convertType(options, Frame.PressOptions.class)));
}
@Override
public Response reload(ReloadOptions options) {
return reloadImpl(options);
return withLogging("Page.reload", () -> reloadImpl(options));
}
@Override
@@ -1059,7 +1096,7 @@ public class PageImpl extends ChannelOwner implements Page {
options = new ReloadOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("reload", params, timeoutSettings.navigationTimeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("reload", params).getAsJsonObject();
if (json.has("response")) {
return connection.getExistingObject(json.getAsJsonObject("response").get("guid").getAsString());
}
@@ -1068,7 +1105,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler, options);
route(new UrlMatcher(browserContext.baseUrl, url), handler, options);
}
@Override
@@ -1087,23 +1124,25 @@ public class PageImpl extends ChannelOwner implements Page {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class), null);
browserContext.recordIntoHar(this, har, convertType(options, BrowserContext.RouteFromHAROptions.class));
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl(), options.url, this.connection.localUtils, false);
UrlMatcher matcher = UrlMatcher.forOneOf(browserContext.baseUrl, options.url);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
}
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
withLogging("Page.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
});
}
@Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, true), handler);
routeWebSocketImpl(new UrlMatcher(browserContext.baseUrl, url), handler);
}
@Override
@@ -1117,13 +1156,15 @@ public class PageImpl extends ChannelOwner implements Page {
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
withLogging("Page.routeWebSocket", () -> {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
});
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return screenshotImpl(options);
return withLogging("Page.screenshot", () -> screenshotImpl(options));
}
@Override
@@ -1140,7 +1181,8 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
@@ -1178,7 +1220,7 @@ public class PageImpl extends ChannelOwner implements Page {
}
params.add("mask", maskArray);
}
JsonObject json = sendMessage("screenshot", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
if (options.path != null) {
@@ -1189,46 +1231,62 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class));
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class));
withLogging("Page.setChecked",
() -> mainFrame.setCheckedImpl(selector, checked, convertType(options, Frame.SetCheckedOptions.class)));
}
@Override
public void setContent(String html, SetContentOptions options) {
mainFrame.setContent(html, convertType(options, Frame.SetContentOptions.class));
withLogging("Page.setContent",
() -> mainFrame.setContentImpl(html, convertType(options, Frame.SetContentOptions.class)));
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
timeoutSettings.setDefaultNavigationTimeout(timeout);
withLogging("Page.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
}
@Override
public void setDefaultTimeout(double timeout) {
timeoutSettings.setDefaultTimeout(timeout);
withLogging("Page.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
}
@Override
public void setExtraHTTPHeaders(Map<String, String> headers) {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
withLogging("Page.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
});
}
@Override
@@ -1238,7 +1296,8 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class));
withLogging("Page.setInputFiles",
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
}
@Override
@@ -1248,30 +1307,35 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class));
withLogging("Page.setInputFiles",
() -> mainFrame.setInputFilesImpl(selector, files, convertType(options, Frame.SetInputFilesOptions.class)));
}
@Override
public void setViewportSize(int width, int height) {
viewport = new ViewportSize(width, height);
JsonObject params = new JsonObject();
params.add("viewportSize", gson().toJsonTree(viewport));
sendMessage("setViewportSize", params, NO_TIMEOUT);
withLogging("Page.setViewportSize", () -> {
viewport = new ViewportSize(width, height);
JsonObject params = new JsonObject();
params.add("viewportSize", gson().toJsonTree(viewport));
sendMessage("setViewportSize", params);
});
}
@Override
public void tap(String selector, TapOptions options) {
mainFrame.tap(selector, convertType(options, Frame.TapOptions.class));
withLogging("Page.tap",
() -> mainFrame.tapImpl(selector, convertType(options, Frame.TapOptions.class)));
}
@Override
public String textContent(String selector, TextContentOptions options) {
return mainFrame.textContent(selector, convertType(options, Frame.TextContentOptions.class));
return withLogging("Page.textContent",
() -> mainFrame.textContentImpl(selector, convertType(options, Frame.TextContentOptions.class)));
}
@Override
public String title() {
return mainFrame.title();
return withLogging("Page.title", () -> mainFrame.titleImpl());
}
@Override
@@ -1281,23 +1345,27 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void type(String selector, String text, TypeOptions options) {
mainFrame.type(selector, text, convertType(options, Frame.TypeOptions.class));
withLogging("Page.type",
() -> mainFrame.typeImpl(selector, text, convertType(options, Frame.TypeOptions.class)));
}
@Override
public void uncheck(String selector, UncheckOptions options) {
mainFrame.uncheck(selector, convertType(options, Frame.UncheckOptions.class));
withLogging("Page.uncheck",
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
}
@Override
public void unrouteAll() {
routes.removeAll();
updateInterceptionPatterns();
withLogging("Page.unrouteAll", () -> {
routes.removeAll();
updateInterceptionPatterns();
});
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), handler);
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
}
@Override
@@ -1311,16 +1379,18 @@ public class PageImpl extends ChannelOwner implements Page {
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
routes.remove(matcher, handler);
updateInterceptionPatterns();
withLogging("Page.unroute", () -> {
routes.remove(matcher, handler);
updateInterceptionPatterns();
});
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns());
}
@Override
@@ -1341,7 +1411,7 @@ public class PageImpl extends ChannelOwner implements Page {
// Note: we are creating Video object lazily, because we do not know
// BrowserContextOptions when constructing the page - it is assigned
// too late during launchPersistentContext.
if (browserContext.videosDir() == null) {
if (browserContext.videosDir == null) {
return null;
}
return forceVideo();
@@ -1362,7 +1432,8 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return mainFrame.waitForFunction(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class));
return withLogging("Page.waitForFunction",
() -> mainFrame.waitForFunctionImpl(pageFunction, arg, convertType(options, Frame.WaitForFunctionOptions.class)));
}
@Override
@@ -1437,7 +1508,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Request waitForRequest(String urlGlob, WaitForRequestOptions options, Runnable code) {
return waitForRequest(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
return waitForRequest(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
@@ -1482,7 +1553,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Response waitForResponse(String urlGlob, WaitForResponseOptions options, Runnable code) {
return waitForResponse(UrlMatcher.forGlob(browserContext.baseUrl(), urlGlob, this.connection.localUtils, false), null, options, code);
return waitForResponse(new UrlMatcher(browserContext.baseUrl, urlGlob), null, options, code);
}
@Override
@@ -1515,7 +1586,8 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return mainFrame.waitForSelector(selector, convertType(options, Frame.WaitForSelectorOptions.class));
return withLogging("Page.waitForSelector",
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
}
@Override
@@ -1529,12 +1601,12 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void waitForTimeout(double timeout) {
mainFrame.waitForTimeout(timeout);
withLogging("Page.waitForTimeout", () -> mainFrame.waitForTimeoutImpl(timeout));
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(UrlMatcher.forGlob(browserContext.baseUrl(), url, this.connection.localUtils, false), options);
waitForURL(new UrlMatcher(browserContext.baseUrl, url), options);
}
@Override
@@ -52,6 +52,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
PlaywrightImpl result = connection.initializePlaywright();
result.driverProcess = p;
result.initSharedSelectors(null);
return result;
} catch (IOException e) {
throw new PlaywrightException("Failed to launch driver", e);
@@ -61,8 +62,9 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
protected SelectorsImpl selectors;
private SharedSelectors sharedSelectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -70,20 +72,26 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
chromium.playwright = this;
firefox.playwright = this;
webkit.playwright = this;
selectors = new SelectorsImpl();
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
apiRequest = new APIRequestImpl(this);
}
public LocalUtils localUtils() {
return connection.localUtils;
void initSharedSelectors(PlaywrightImpl parent) {
assert sharedSelectors == null;
if (parent == null) {
sharedSelectors = new SharedSelectors();
} else {
sharedSelectors = parent.sharedSelectors;
}
sharedSelectors.addChannel(selectors);
}
void unregisterSelectors() {
sharedSelectors.removeChannel(selectors);
}
public JsonArray deviceDescriptors() {
return localUtils().deviceDescriptors();
return connection.localUtils.deviceDescriptors();
}
@Override
@@ -108,7 +116,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
@Override
public Selectors selectors() {
return selectors;
return sharedSelectors;
}
@Override
@@ -14,6 +14,8 @@
* limitations under the License.
*/
// This file is generated by generate_java_rpc.js, do not edit manually.
package com.microsoft.playwright.impl;
import java.util.List;
@@ -103,7 +105,6 @@ class ExpectedTextValue {
class FrameExpectOptions {
Object expressionArg;
List<ExpectedTextValue> expectedText;
String selector;
Double expectedNumber;
SerializedArgument expectedValue;
Boolean useInnerText;
@@ -26,4 +26,8 @@ public class RemoteBrowser extends ChannelOwner {
BrowserImpl browser() {
return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
}
SelectorsImpl selectors() {
return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
}
}
@@ -53,6 +53,7 @@ public class RequestImpl extends ChannelOwner implements Request {
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
if (initializer.has("redirectedFrom")) {
redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
@@ -68,7 +69,7 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public Map<String, String> allHeaders() {
return getRawHeaders().headers();
return withLogging("Request.allHeaders", () -> getRawHeaders().headers());
}
@Override
@@ -97,12 +98,12 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public List<HttpHeader> headersArray() {
return getRawHeaders().headersArray();
return withLogging("Request.headersArray", () -> getRawHeaders().headersArray());
}
@Override
public String headerValue(String name) {
return getRawHeaders().get(name);
return withLogging("Request.headerValue", () -> getRawHeaders().get(name));
}
@Override
@@ -152,21 +153,25 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public ResponseImpl response() {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
return withLogging("Request.response", () -> {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
});
}
@Override
public Sizes sizes() {
ResponseImpl response = response();
if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request");
}
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
return withLogging("Request.sizes", () -> {
ResponseImpl response = response();
if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request");
}
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
});
}
@Override
@@ -193,8 +198,10 @@ public class RequestImpl extends ChannelOwner implements Request {
if (rawHeaders != null) {
return rawHeaders;
}
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
JsonArray rawHeadersJson = result.getAsJsonArray("headers");
JsonArray rawHeadersJson = withLogging("Request.allHeaders", () -> {
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
return result.getAsJsonArray("headers");
});
// The field may have been initialized in a nested call but it is ok.
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
@@ -40,6 +40,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
@@ -47,13 +48,15 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public Map<String, String> allHeaders() {
return getRawHeaders().headers();
return withLogging("Response.allHeaders", () -> getRawHeaders().headers());
}
@Override
public byte[] body() {
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
return withLogging("Response.body", () -> {
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
});
}
@Override
@@ -93,7 +96,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public List<HttpHeader> headersArray() {
return getRawHeaders().headersArray();
return withLogging("Response.headersArray", () -> getRawHeaders().headersArray());
}
@Override
@@ -118,20 +121,24 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public SecurityDetails securityDetails() {
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class);
}
return null;
return withLogging("Response.securityDetails", () -> {
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class);
}
return null;
});
}
@Override
public ServerAddr serverAddr() {
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class);
}
return null;
return withLogging("Response.serverAddr", () -> {
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class);
}
return null;
});
}
@Override
@@ -38,15 +38,18 @@ public class RouteImpl extends ChannelOwner implements Route {
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
@Override
public void abort(String errorCode) {
startHandling();
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessageAsync("abort", params);
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessageAsync("abort", params);
});
}
boolean isHandled() {
@@ -61,7 +64,7 @@ public class RouteImpl extends ChannelOwner implements Route {
void resume(ResumeOptions options, boolean isFallback) {
startHandling();
applyOverrides(convertType(options, FallbackOptions.class));
resumeImpl(request().fallbackOverridesForResume(), isFallback);
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume(), isFallback));
}
@Override
@@ -149,7 +152,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void fulfill(FulfillOptions options) {
startHandling();
fulfillImpl(options);
withLogging("Route.fulfill", () -> fulfillImpl(options));
}
private void fulfillImpl(FulfillOptions options) {
@@ -17,72 +17,28 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SelectorsImpl extends LoggingSupport implements Selectors {
protected final List<BrowserContextImpl> contextsForSelectors = new ArrayList<>();
protected final List<JsonObject> selectorEngines = new ArrayList<>();
String testIdAttributeName = "data-testid";
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
for (BrowserContextImpl context : contextsForSelectors) {
try {
JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", attributeName);
context.sendMessageAsync("setTestIdAttributeName", params);
} catch (PlaywrightException e) {
}
}
class SelectorsImpl extends ChannelOwner {
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void register(String name, String script, RegisterOptions options) {
registerImpl(name, script, options);
void register(String name, String script, Selectors.RegisterOptions options) {
if (options == null) {
options = new Selectors.RegisterOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
params.addProperty("source", script);
sendMessage("register", params);
}
@Override
public void register(String name, Path path, RegisterOptions options) {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
}
private void registerImpl(String name, String script, RegisterOptions options) {
if (selectorEngines.stream().anyMatch(engine -> name.equals(engine.get("name").getAsString()))) {
throw new PlaywrightException("selectors.register: \"" + name + "\" selector engine has been already registered");
}
JsonObject engine = new JsonObject();
engine.addProperty("name", name);
engine.addProperty("source", script);
if (options != null && options.contentScript != null) {
engine.addProperty("contentScript", options.contentScript);
}
for (BrowserContextImpl context : contextsForSelectors) {
JsonObject params = new JsonObject();
params.add("selectorEngine", engine);
context.sendMessage("registerSelectorEngine", params, NO_TIMEOUT);
}
selectorEngines.add(engine);
void setTestIdAttributeName(String name) {
JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", name);
sendMessageAsync("setTestIdAttributeName", params);
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SharedSelectors extends LoggingSupport implements Selectors {
private final List<SelectorsImpl> channels = new ArrayList<>();
private final List<Registration> registrations = new ArrayList<>();
String testIdAttributeName = "data-testid";
private static class Registration {
final String name;
final String script;
final RegisterOptions options;
Registration(String name, String script, RegisterOptions options) {
this.name = name;
this.script = script;
this.options = options;
}
}
@Override
public void register(String name, String script, RegisterOptions options) {
withLogging("Selectors.register", () -> registerImpl(name, script, options));
}
@Override
public void register(String name, Path path, RegisterOptions options) {
withLogging("Selectors.register", () -> {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
});
}
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
channels.forEach(channel -> channel.setTestIdAttributeName(testIdAttributeName));
}
void addChannel(SelectorsImpl channel) {
registrations.forEach(r -> {
try {
channel.register(r.name, r.script, r.options);
} catch (PlaywrightException e) {
// This should not fail except for connection closure, but just in case we catch.
}
channel.setTestIdAttributeName(testIdAttributeName);
});
channels.add(channel);
}
void removeChannel(SelectorsImpl channel) {
channels.remove(channel);
}
private void registerImpl(String name, String script, RegisterOptions options) {
channels.forEach(impl -> impl.register(name, script, options));
registrations.add(new Registration(name, script, options));
}
}
@@ -50,7 +50,7 @@ public class Stream extends ChannelOwner {
}
JsonObject params = new JsonObject();
params.addProperty("size", len);
JsonObject json = sendMessage("read", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("read", params).getAsJsonObject();
String encoded = json.get("binary").getAsString();
if (encoded.isEmpty()) {
return -1;
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl;
class TimeoutSettings {
private static final int DEFAULT_TIMEOUT_MS = 30_000;
private static final int DEFAULT_LAUNCH_TIMEOUT_MS = 180_000;
private final TimeoutSettings parent;
private Double defaultTimeout ;
@@ -81,11 +80,4 @@ class TimeoutSettings {
}
return new WaitableTimeout<>(timeout(timeout));
}
static double launchTimeout(Double timeout) {
if (timeout != null) {
return timeout;
}
return DEFAULT_LAUNCH_TIMEOUT_MS;
}
}
@@ -19,8 +19,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Touchscreen;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class TouchscreenImpl implements Touchscreen {
private final PageImpl page;
@@ -30,9 +28,11 @@ class TouchscreenImpl implements Touchscreen {
@Override
public void tap(double x, double y) {
JsonObject params = new JsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("touchscreenTap", params, NO_TIMEOUT);
page.withLogging("Touchscreen.tap", () -> {
JsonObject params = new JsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("touchscreenTap", params);
});
}
}
@@ -33,6 +33,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
private void stopChunkImpl(Path path) {
@@ -45,7 +46,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
// Not interested in artifacts.
if (path == null) {
params.addProperty("mode", "discard");
sendMessage("tracingStopChunk", params, NO_TIMEOUT);
sendMessage("tracingStopChunk", params);
if (stacksId != null) {
connection.localUtils().traceDiscarded(stacksId);
}
@@ -55,14 +56,14 @@ class TracingImpl extends ChannelOwner implements Tracing {
boolean isLocal = !connection.isRemote;
if (isLocal) {
params.addProperty("mode", "entries");
JsonObject json = sendMessage("tracingStopChunk", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
JsonArray entries = json.getAsJsonArray("entries");
connection.localUtils.zip(path, entries, stacksId, false, includeSources);
return;
}
params.addProperty("mode", "archive");
JsonObject json = sendMessage("tracingStopChunk", params, NO_TIMEOUT).getAsJsonObject();
JsonObject json = sendMessage("tracingStopChunk", params).getAsJsonObject();
// The artifact may be missing if the browser closed while stopping tracing.
if (!json.has("artifact")) {
if (stacksId != null) {
@@ -87,7 +88,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
@Override
public void group(String name, GroupOptions options) {
groupImpl(name, options);
withLogging("Tracing.group", () -> groupImpl(name, options));
}
private void groupImpl(String name, GroupOptions options) {
@@ -96,12 +97,12 @@ class TracingImpl extends ChannelOwner implements Tracing {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
sendMessage("tracingGroup", params, NO_TIMEOUT);
sendMessage("tracingGroup", params);
}
@Override
public void groupEnd() {
sendMessage("tracingGroupEnd");
withLogging("Tracing.groupEnd", () -> sendMessage("tracingGroupEnd"));
}
private void tracingStartChunk(String name, String title) {
@@ -112,7 +113,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
if (title != null) {
params.addProperty("title", title);
}
JsonObject result = sendMessage("tracingStartChunk", params, NO_TIMEOUT).getAsJsonObject();
JsonObject result = sendMessage("tracingStartChunk", params).getAsJsonObject();
startCollectingStacks(result.get("traceName").getAsString());
}
@@ -134,7 +135,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
if (includeSources) {
params.addProperty("sources", true);
}
sendMessage("tracingStart", params, NO_TIMEOUT);
sendMessage("tracingStart", params);
tracingStartChunk(options.name, options.title);
}
@@ -21,22 +21,25 @@ import com.microsoft.playwright.PlaywrightException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.globToRegex;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class UrlMatcher {
private final String baseURL;
public final String glob;
public final Pattern pattern;
public final Predicate<String> predicate;
static UrlMatcher forOneOf(URL baseUrl, Object object, LocalUtils localUtils, boolean isWebSocketUrl) {
static UrlMatcher forOneOf(URL baseUrl, Object object) {
if (object == null) {
return new UrlMatcher(null, null, null);
return new UrlMatcher(null, null, null, null);
}
if (object instanceof String) {
return UrlMatcher.forGlob(baseUrl, (String) object, localUtils, isWebSocketUrl);
return new UrlMatcher(baseUrl, (String) object);
}
if (object instanceof Pattern) {
return new UrlMatcher((Pattern) object);
@@ -63,32 +66,61 @@ class UrlMatcher {
}
}
static UrlMatcher forGlob(URL baseURL, String glob, LocalUtils localUtils, boolean isWebSocketUrl) {
Pattern pattern = localUtils.globToRegex(glob, baseURL != null ? baseURL.toString() : null, isWebSocketUrl);
return new UrlMatcher(glob, pattern, null);
private static String normaliseUrl(String spec) {
try {
// Align with the Node.js URL parser which automatically adds a slash to the path if it is empty.
URI url = new URI(spec);
if (url.getScheme() != null &&
Arrays.asList("http", "https", "ws", "wss").contains(url.getScheme()) &&
url.getPath().isEmpty()) {
return new URI(url.getScheme(), url.getAuthority(), "/", url.getQuery(), url.getFragment()).toString();
}
return url.toString();
} catch (URISyntaxException e) {
return spec;
}
}
UrlMatcher(URL baseURL, String glob) {
this(baseURL, glob, null, null);
}
UrlMatcher(Pattern pattern) {
this(null, pattern, null);
this(null, null, pattern, null);
}
UrlMatcher(Predicate<String> predicate) {
this(null, null, predicate);
this(null, null, null, predicate);
}
private UrlMatcher(String glob, Pattern pattern, Predicate<String> predicate) {
private UrlMatcher(URL baseURL, String glob, Pattern pattern, Predicate<String> predicate) {
this.baseURL = baseURL != null ? baseURL.toString() : null;
this.glob = glob;
this.pattern = pattern;
this.predicate = predicate;
}
boolean test(String value) {
return testImpl(baseURL, pattern, predicate, glob, value);
}
private static boolean testImpl(String baseURL, Pattern pattern, Predicate<String> predicate, String glob, String value) {
if (pattern != null) {
return pattern.matcher(value).find();
}
if (predicate != null) {
return predicate.test(value);
}
if (glob != null) {
if (!glob.startsWith("*")) {
// Allow http(s) baseURL to match ws(s) urls.
if (baseURL != null && Pattern.compile("^https?://").matcher(baseURL).find() && Pattern.compile("^wss?://").matcher(value).find()) {
baseURL = baseURL.replaceFirst("^http", "ws");
}
glob = normaliseUrl(resolveUrl(baseURL, glob));
}
return Pattern.compile(globToRegex(glob)).matcher(value).find();
}
return true;
}
@@ -125,12 +157,10 @@ class UrlMatcher {
@Override
public String toString() {
if (glob != null)
return String.format("<glob pattern=\"%s\">", glob);
if (pattern != null)
return String.format("<regex pattern=\"%s\" flags=\"%s\">", pattern.pattern(), toJsRegexFlags(pattern));
if (this.predicate != null)
return "<predicate>";
return "<true>";
return String.format("<glob pattern=\"%s\">", glob);
}
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.ClientCertificate;
@@ -31,11 +32,11 @@ 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;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
import static java.nio.file.Files.readAllBytes;
@@ -90,6 +91,79 @@ public class Utils {
return convertType(f, (Class<T>) f.getClass());
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
static Set<Character> escapeGlobChars = new HashSet<>(Arrays.asList('$', '^', '+', '.', '*', '(', ')', '|', '\\', '?', '{', '}', '[', ']'));
static String globToRegex(String glob) {
StringBuilder tokens = new StringBuilder();
tokens.append('^');
boolean inGroup = false;
for (int i = 0; i < glob.length(); ++i) {
char c = glob.charAt(i);
if (c == '\\' && i + 1 < glob.length()) {
char nextChar = glob.charAt(++i);
if (escapeGlobChars.contains(nextChar)) {
tokens.append('\\');
}
tokens.append(nextChar);
continue;
}
if (c == '*') {
boolean beforeDeep = i < 1 || glob.charAt(i - 1) == '/';
int starCount = 1;
while (i + 1 < glob.length() && glob.charAt(i + 1) == '*') {
starCount++;
i++;
}
boolean afterDeep = i + 1 >= glob.length() || glob.charAt(i + 1) == '/';
boolean isDeep = starCount > 1 && beforeDeep && afterDeep;
if (isDeep) {
tokens.append("((?:[^/]*(?:\\/|$))*)");
i++;
} else {
tokens.append("([^/]*)");
}
continue;
}
switch (c) {
case '?':
tokens.append('.');
break;
case '[':
tokens.append('[');
break;
case ']':
tokens.append(']');
break;
case '{':
inGroup = true;
tokens.append('(');
break;
case '}':
inGroup = false;
tokens.append(')');
break;
case ',':
if (inGroup) {
tokens.append('|');
break;
}
tokens.append("\\").append(c);
break;
default:
if (escapeGlobChars.contains(c)) {
tokens.append('\\');
}
tokens.append(c);
break;
}
}
tokens.append('$');
return tokens.toString();
}
static String mimeType(Path path) {
String mimeType;
try {
@@ -201,7 +275,7 @@ public class Utils {
items.add(item);
}
tempFilesParams.add("items", items);
return context.sendMessage("createTempFiles", tempFilesParams, NO_TIMEOUT).getAsJsonObject();
return context.sendMessage("createTempFiles", tempFilesParams).getAsJsonObject();
}
static void checkFilePayloadSize(FilePayload[] files) {
@@ -30,6 +30,7 @@ class VideoImpl implements Video {
VideoImpl(PageImpl page) {
this.page = page;
BrowserImpl browser = page.context().browser();
}
void setArtifact(ArtifactImpl artifact) {
@@ -43,33 +44,39 @@ class VideoImpl implements Video {
@Override
public void delete() {
try {
waitForArtifact().delete();
} catch (PlaywrightException e) {
}
page.withLogging("Video.delete", () -> {
try {
waitForArtifact().delete();
} catch (PlaywrightException e) {
}
});
}
@Override
public Path path() {
if (page.connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
}
try {
return Paths.get(waitForArtifact().initializer.get("absolutePath").getAsString());
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
return page.withLogging("Video.path", () -> {
if (page.connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use saveAs() to save a local copy.");
}
try {
return Paths.get(waitForArtifact().initializer.get("absolutePath").getAsString());
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
});
}
@Override
public void saveAs(Path path) {
if (!page.isClosed()) {
throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs");
}
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
page.withLogging("Video.saveAs", () -> {
if (!page.isClosed()) {
throw new PlaywrightException("Page is not yet closed. Close the page prior to calling saveAs");
}
try {
waitForArtifact().saveAs(path);
} catch (PlaywrightException e) {
throw new PlaywrightException("Page did not produce any video frames", e);
}
});
}
}
@@ -38,21 +38,23 @@ public class WaitForEventLogger<T> implements Supplier<T>, Logger {
@Override
public T get() {
{
return channel.withLogging(apiName, () -> {
{
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
}
JsonObject info = new JsonObject();
info.addProperty("phase", "before");
sendWaitForEventInfo(info);
}
JsonObject info = new JsonObject();
info.addProperty("phase", "after");
try {
return supplier.apply(this);
} catch (RuntimeException e) {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
}
info.addProperty("phase", "after");
try {
return supplier.apply(this);
} catch (RuntimeException e) {
info.addProperty("error", e.getMessage());
throw e;
} finally {
sendWaitForEventInfo(info);
}
});
}
@Override
@@ -69,6 +69,7 @@ class WebSocketRouteImpl extends ChannelOwner implements WebSocketRoute {
WebSocketRouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
@Override
@@ -128,11 +129,7 @@ class WebSocketRouteImpl extends ChannelOwner implements WebSocketRoute {
return;
}
// Ensure that websocket is "open" and can send messages without an actual server connection.
try {
sendMessageAsync("ensureOpened");
} catch (PlaywrightException e) {
// If this happens after the page has been closed, ignore the error.
}
sendMessageAsync("ensureOpened");
}
@Override
@@ -71,21 +71,25 @@ class WorkerImpl extends ChannelOwner implements Worker {
@Override
public Object evaluate(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
return withLogging("Worker.evaluate", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
return withLogging("Worker.evaluateHandle", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
});
}
@Override
@@ -42,7 +42,7 @@ class WritableStream extends ChannelOwner {
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
ByteBuffer encoded = Base64.getEncoder().encode(buffer);
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
sendMessage("write", params, NO_TIMEOUT);
sendMessage("write", params);
}
@Override
@@ -48,12 +48,6 @@ public class Cookie {
* Optional.
*/
public SameSiteAttribute sameSite;
/**
* For partitioned third-party cookies (aka <a
* href="https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies">CHIPS</a>), the
* partition key. Optional.
*/
public String partitionKey;
public Cookie(String name, String value) {
this.name = name;
@@ -109,13 +103,4 @@ public class Cookie {
this.sameSite = sameSite;
return this;
}
/**
* For partitioned third-party cookies (aka <a
* href="https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies">CHIPS</a>), the
* partition key. Optional.
*/
public Cookie setPartitionKey(String partitionKey) {
this.partitionKey = partitionKey;
return this;
}
}
@@ -218,7 +218,7 @@ public class Server implements HttpHandler {
}
long contentLength = body.size();
// -1 means no body, 0 means chunked encoding.
exchange.sendResponseHeaders(200, (contentLength == 0 || exchange.getRequestMethod().equals("HEAD")) ? -1 : contentLength);
exchange.sendResponseHeaders(200, contentLength == 0 ? -1 : contentLength);
if (contentLength > 0) {
exchange.getResponseBody().write(body.toByteArray());
}
@@ -33,6 +33,7 @@ public class TestBrowserContextCredentials extends TestBase {
@Test
@DisabledIf(value="isChromiumHeadedLike", disabledReason="fail")
void shouldFailWithoutCredentials() {
System.out.println("channel2 " + getBrowserChannelFromEnv());
server.setAuth("/empty.html", "user", "pass");
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(401, response.status());
@@ -436,7 +436,7 @@ public class TestBrowserContextFetch extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.request().get(server.PREFIX + "/slow", RequestOptions.create().setTimeout(100));
});
assertTrue(e.getMessage().contains("Timeout 100ms exceeded"), e.getMessage());
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
@Test
@@ -468,7 +468,7 @@ public class TestBrowserContextFetch extends TestBase {
context.setDefaultTimeout(100);
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect"));
assertTrue(e.getMessage().contains("Timeout 100ms exceeded"), e.getMessage());
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
@Test
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.HarContentPolicy;
import com.microsoft.playwright.options.HarMode;
import com.microsoft.playwright.options.HarNotFound;
import com.microsoft.playwright.options.RouteFromHarUpdateContentPolicy;
@@ -39,6 +40,7 @@ import static com.microsoft.playwright.Utils.copy;
import static com.microsoft.playwright.Utils.extractZip;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static com.microsoft.playwright.options.HarContentPolicy.ATTACH;
import static com.microsoft.playwright.options.HarContentPolicy.EMBED;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextHar extends TestBase {
@@ -174,19 +174,10 @@ public class TestBrowserContextStorageState extends TestBase {
@Test
void shouldSupportIndexedDB() {
page.navigate(server.PREFIX + "/to-do-notifications/index.html");
assertThat(page.locator("#notifications")).matchesAriaSnapshot(
" - list:\n" +
" - listitem: Database initialised."
);
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();
assertThat(page.locator("#notifications")).matchesAriaSnapshot(
" - list:\n" +
" - listitem: \"Transaction completed: database modification finished.\""
);
String storageState = page.context().storageState(new BrowserContext.StorageStateOptions().setIndexedDB(true));
assertJsonEquals("{\"cookies\":[],\"origins\":[\n" +
@@ -204,18 +195,14 @@ public class TestBrowserContextStorageState extends TestBase {
" \"keyPath\": \"taskTitle\",\n" +
" \"records\": [\n" +
" {\n" +
" \"valueEncoded\": {\n" +
" \"id\": 1,\n" +
" \"o\": [\n" +
" {\"k\": \"taskTitle\", \"v\": \"Pet the cat\"},\n" +
" {\"k\": \"hours\", \"v\": \"1\"},\n" +
" {\"k\": \"minutes\", \"v\": \"1\"},\n" +
" {\"k\": \"day\", \"v\": \"01\"},\n" +
" {\"k\": \"month\", \"v\": \"January\"},\n" +
" {\"k\": \"year\", \"v\": \"2025\"},\n" +
" {\"k\": \"notified\", \"v\": \"no\"},\n" +
" {\"k\": \"signature\", \"v\": { \"ta\": {\"b\":\"c2lnbmVkIGJ5IHNpbW9u\",\"k\":\"ui8\"}}}\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" +
@@ -57,35 +57,18 @@ public class TestChromiumTracing extends TestBase {
}
}
private static void rafraf(Page page) {
int count = 2;
for (int i = 0; i < count; i++) {
page.evaluate("() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))");
}
}
@Test
void shouldRunWithCustomCategoriesIfProvided(@TempDir Path tempDir) throws IOException {
try (Page page = browser.newPage()) {
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile)
.setCategories(asList("disabled-by-default-cc.debug")));
rafraf(page);
.setCategories(asList("disabled-by-default-v8.cpu_profiler.hires")));
browser.stopTracing();
try (FileReader fileReader = new FileReader(outputTraceFile.toFile())) {
JsonObject traceJson = new Gson().fromJson(fileReader, JsonObject.class);
// NOTE: trace-config is deprecated as per http://crrev.com/c/6628182
boolean hasTraceConfig =
traceJson.getAsJsonObject("metadata").get("trace-config") != null
&& traceJson.getAsJsonObject("metadata").get("trace-config").getAsString().contains("disabled-by-default-cc.debug");
boolean hasTraceEvents = traceJson.getAsJsonArray("traceEvents").asList().stream()
.anyMatch(event -> {
JsonObject eventObj = (JsonObject) event;
return eventObj.has("cat") &&
eventObj.get("cat").getAsString().equals("disabled-by-default-cc.debug");
});
assertTrue(hasTraceConfig || hasTraceEvents);
assertTrue(traceJson.getAsJsonObject("metadata").get("trace-config")
.getAsString().contains("disabled-by-default-v8.cpu_profiler.hires"));
}
}
}
@@ -125,8 +125,8 @@ public class TestClientCertificates extends TestBase {
.setIgnoreHTTPSErrors(true) // TODO: remove once we can pass a custom CA.
.setClientCertificates(asList(
new ClientCertificate(customServer.origin)
.setPfxPath(asset("client-certificates/client/trusted/keystore.p12"))
.setPassphrase("")));
.setPfxPath(asset("client-certificates/client/trusted/client_keystore.p12"))
.setPassphrase("passphrase")));
APIRequestContext request = playwright.request().newContext(requestOptions);
APIResponse response = request.get(customServer.url);
@@ -307,19 +307,4 @@ public class TestDefaultBrowserContext2 extends TestBase {
assertTrue(Files.list(userDataDir).count() > 0);
context.close();
}
@Test
void shouldExposeBrowser() {
Page page = launchPersistent();
BrowserContext context = page.context();
Browser browser = context.browser();
assertFalse(browser.version().isEmpty());
Page page2 = browser.newPage();
page2.navigate("data:text/html,<html><title>Title</title></html>");
assertEquals("Title", page2.title());
browser.close();
assertEquals(0, context.pages().size());
// Next line should not throw.
context.close();
}
}
@@ -28,7 +28,7 @@ public class TestElementHandleSelectText extends TestBase {
ElementHandle textarea = page.querySelector("textarea");
textarea.evaluate("textarea => textarea.value = 'some value'");
textarea.selectText();
if (isFirefox()) {
if (isFirefox() || isWebKit()) {
assertEquals(0, textarea.evaluate("el => el.selectionStart"));
assertEquals(10, textarea.evaluate("el => el.selectionEnd"));
} else {
@@ -42,7 +42,7 @@ public class TestElementHandleSelectText extends TestBase {
ElementHandle input = page.querySelector("input");
input.evaluate("input => input.value = 'some value'");
input.selectText();
if (isFirefox()) {
if (isFirefox() || isWebKit()) {
assertEquals(0, input.evaluate("el => el.selectionStart"));
assertEquals(10, input.evaluate("el => el.selectionEnd"));
} else {
@@ -17,7 +17,6 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.microsoft.playwright.APIRequest.NewContextOptions;
import com.microsoft.playwright.options.HttpCredentials;
import com.microsoft.playwright.options.HttpCredentialsSend;
import com.microsoft.playwright.options.HttpHeader;
@@ -38,8 +37,6 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestGlobalFetch extends TestBase {
private static final List<String> HTTP_METHODS = asList("GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH");
@Test
void shouldHaveJavaInDefaultUesrAgent() throws ExecutionException, InterruptedException {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions());
@@ -189,7 +186,7 @@ public class TestGlobalFetch extends TestBase {
APIRequestContext request = playwright.request().newContext(new APIRequest.NewContextOptions().setTimeout(100));
server.setRoute("/empty.html", exchange -> {});
PlaywrightException e = assertThrows(PlaywrightException.class, () -> request.get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Timeout 100ms exceeded"), e.getMessage());
assertTrue(e.getMessage().contains("Request timed out after 100ms"), e.getMessage());
}
@@ -340,7 +337,7 @@ public class TestGlobalFetch extends TestBase {
for (String method : new String[] {"head", "put", "trace"}) {
server.setRoute("/empty.html", exchange -> {
exchange.getResponseHeaders().set("Content-type", "text/plain");
exchange.sendResponseHeaders(404, exchange.getRequestMethod().equals("HEAD") ? -1 : 10);
exchange.sendResponseHeaders(404, 10);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("Not found.");
}
@@ -361,7 +358,7 @@ public class TestGlobalFetch extends TestBase {
server.setRedirect("/b/c/redirect4", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : HTTP_METHODS) {
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
for (int maxRedirects = 1; maxRedirects < 4; maxRedirects++) {
int currMaxRedirects = maxRedirects;
PlaywrightException exception = assertThrows(PlaywrightException.class,
@@ -373,69 +370,13 @@ public class TestGlobalFetch extends TestBase {
request.dispose();
}
@Test
void shouldUseMaxRedirectsFromFetchWhenProvidedOverridingNewContext() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/b/c/redirect3");
server.setRedirect("/b/c/redirect3", "/b/c/redirect4");
server.setRedirect("/b/c/redirect4", "/simple.json");
APIRequestContext request = playwright.request().newContext(new NewContextOptions().setMaxRedirects(1));
for (String method : HTTP_METHODS) {
APIResponse response = request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(4));
assertEquals(200, response.status());
}
request.dispose();
}
@Test
void shouldFollowRedirectsUpToMaxRedirectsLimitSetInNewContext() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/b/c/redirect3");
server.setRedirect("/b/c/redirect3", "/b/c/redirect4");
server.setRedirect("/b/c/redirect4", "/simple.json");
for (String method : HTTP_METHODS) {
for (int maxRedirects = 1; maxRedirects <= 4; maxRedirects++) {
int currMaxRedirects = maxRedirects;
APIRequestContext request = playwright.request().newContext(new NewContextOptions().setMaxRedirects(currMaxRedirects));
if (maxRedirects < 4) {
PlaywrightException exception = assertThrows(PlaywrightException.class,
() -> request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method)));
assertTrue(exception.getMessage().contains("Max redirect count exceeded"), exception.getMessage());
} else {
APIResponse response = request.fetch(server.PREFIX + "/a/redirect1", RequestOptions.create().setMethod(method));
assertEquals(200, response.status());
}
request.dispose();
}
}
}
@Test
void shouldNotFollowRedirectsWhenMaxRedirectsIsSetTo0InNewContext() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext(new NewContextOptions().setMaxRedirects(0));
for (String method : HTTP_METHODS) {
APIResponse response = request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method));
assertEquals("/b/c/redirect2", response.headers().get("location"));
assertEquals(302, response.status());
}
request.dispose();
}
@Test
void shouldNotFollowRedirectsWhenMaxRedirectsIsSetTo0() {
server.setRedirect("/a/redirect1", "/b/c/redirect2");
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : HTTP_METHODS) {
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
APIResponse response = request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(0));
assertEquals("/b/c/redirect2", response.headers().get("location"));
@@ -450,7 +391,7 @@ public class TestGlobalFetch extends TestBase {
server.setRedirect("/b/c/redirect2", "/simple.json");
APIRequestContext request = playwright.request().newContext();
for (String method : HTTP_METHODS) {
for (String method : new String[] {"GET", "PUT", "POST", "OPTIONS", "HEAD", "PATCH"}) {
PlaywrightException exception = assertThrows(PlaywrightException.class,
() -> request.fetch(server.PREFIX + "/a/redirect1",
RequestOptions.create().setMethod(method).setMaxRedirects(-1)));
@@ -18,12 +18,10 @@ package com.microsoft.playwright;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.assertions.PlaywrightAssertions;
import com.microsoft.playwright.assertions.LocatorAssertions.ContainsClassOptions;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
import static java.util.Arrays.asList;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.mapOf;
@@ -1070,7 +1068,7 @@ public class TestLocatorAssertions extends TestBase {
Locator locator = page.locator("div");
PlaywrightAssertions.setDefaultAssertionTimeout(1000);
AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertThat(locator).hasText("foo"));
assertTrue(exception.getMessage().contains("Assert \"hasText\" with timeout 1000ms"), exception.getMessage());
assertTrue(exception.getMessage().contains("Locator.expect with timeout 1000ms"), exception.getMessage());
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@@ -1094,49 +1092,4 @@ public class TestLocatorAssertions extends TestBase {
// Restore default.
PlaywrightAssertions.setDefaultAssertionTimeout(5_000);
}
@Test
void containsClassPass() {
page.setContent("<div class='foo bar baz'></div>");
Locator locator = page.locator("div");
assertThat(locator).containsClass("");
assertThat(locator).containsClass("bar");
assertThat(locator).containsClass("baz bar");
assertThat(locator).containsClass(" bar foo ");
assertThat(locator).not().containsClass(" baz not-matching");
}
@Test
void containsClassPassWithSvgs() {
page.setContent("<svg class='c1 c2' role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'></svg>");
assertThat(page.locator("svg")).containsClass("c1");
assertThat(page.locator("svg")).containsClass("c2 c1");
}
@Test
void containsClassFail() {
page.setContent("<div class='bar baz'></div>");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(page.locator("div")).containsClass("does-not-exist", new ContainsClassOptions().setTimeout(1000));
});
assertTrue(e.getMessage().contains("Assert \"containsClass\" with timeout 1000ms"), e.getMessage());
}
@Test
void containsClassPassWithArray() {
page.setContent("<div class='foo'></div><div class='hello bar'></div><div class='baz'></div>");
Locator locator = page.locator("div");
assertThat(locator).containsClass(asList("foo", "hello", "baz"));
assertThat(locator).not().hasClass(new String[]{"not-there", "hello", "baz"}); // Class not there
assertThat(locator).not().hasClass(new String[]{"foo", "hello"}); // length mismatch
}
@Test
void containsClassFailWithArray() {
page.setContent("<div class='foo'></div><div class='bar'></div><div class='bar'></div>");
AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> {
assertThat(page.locator("div")).containsClass(asList("foo", "bar", "baz"), new ContainsClassOptions().setTimeout(1000));
});
assertTrue(e.getMessage().contains("Assert \"containsClass\" with timeout 1000ms"), e.getMessage());
}
}
@@ -218,6 +218,6 @@ public class TestLocatorAssertions2 extends TestBase {
AssertionFailedError e = assertThrows(AssertionFailedError.class, () ->
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setIndeterminate(true).setTimeout(1000)));
// TODO: should be "assertThat().isChecked() with timeout 1000ms"
assertTrue(e.getMessage().contains("Assert \"isChecked\" with timeout 1000ms"), e.getMessage());
assertTrue(e.getMessage().contains("Locator.expect with timeout 1000ms"), e.getMessage());
}
}
@@ -271,7 +271,7 @@ public class TestPageAddLocatorHandler extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.locator("#target").click(new Locator.ClickOptions().setTimeout(3_000)));
assertEquals(0, (int) page.evaluate("window.clicked"));
assertTrue(page.locator("#interstitial").isVisible());
assertThat(page.locator("#interstitial")).isVisible();
assertEquals(1, called[0]);
assertTrue(e.getMessage().contains("locator handler has finished, waiting for getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(\"close\")) to be hidden"), e.getMessage());
}
@@ -3,7 +3,6 @@ package com.microsoft.playwright;
import com.microsoft.playwright.junit.FixtureTest;
import com.microsoft.playwright.junit.UsePlaywright;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.util.Arrays;
import java.util.List;
@@ -13,7 +12,6 @@ import java.util.stream.Collectors;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@FixtureTest
@UsePlaywright
@@ -69,7 +67,7 @@ public class TestPageAriaSnapshot {
@Test
void shouldSnapshotComplex(Page page) {
page.setContent("<ul><li><a href='about:blank'>link</a></li></ul>");
checkAndMatchSnapshot(page.locator("body"), "- list:\n - listitem:\n - link \"link\":\n - /url: about:blank");
checkAndMatchSnapshot(page.locator("body"), "- list:\n - listitem:\n - link \"link\"");
}
@Test
@@ -85,46 +83,4 @@ public class TestPageAriaSnapshot {
page.setContent("<details><summary>Summary</summary><div>Details</div></details>");
checkAndMatchSnapshot(page.locator("body"), "- group: Summary");
}
@Test
void shouldSnapshotChildren(Page page) {
page.setContent("<ul><li><img />One</li><li>Two</li><li>Three</li></ul>");
assertThat(page.locator("body")).matchesAriaSnapshot("- list:\n - /children: equal\n - listitem\n - listitem: Two\n - listitem: Three");
assertThat(page.locator("body")).not().matchesAriaSnapshot("- list:\n - /children: equal\n - listitem\n - listitem: Two");
assertThat(page.locator("body")).matchesAriaSnapshot("- list:\n - /children: deep-equal\n - listitem:\n - img\n - text: One\n - listitem: Two\n - listitem: Three");
assertThat(page.locator("body")).not().matchesAriaSnapshot("- list:\n - /children: deep-equal\n - listitem:\n - text: One\n - listitem: Two\n - listitem: Three");
assertThat(page.locator("body")).matchesAriaSnapshot("- list:\n - /children: deep-equal\n - listitem:\n - /children: contain\n - text: One\n - listitem: Two\n - listitem: Three");
}
@Test
void shouldMatchUrl(Page page) {
page.setContent("<a href='https://example.com'>Link</a>");
assertThat(page.locator("body")).matchesAriaSnapshot("" +
"- link:\n" +
" - /url: /.*example.com/");
}
@Test
void shouldHandleTopLevelDeepEqual(Page page) {
// https://github.com/microsoft/playwright/issues/36456
page.setContent("" +
"<ul>\n" +
" <li>\n" +
" <ul>\n" +
" <li>1.1</li>\n" +
" <li>1.2</li>\n" +
" </ul>\n" +
" </li>\n" +
"</ul>");
assertThrows(AssertionFailedError.class, () -> {
assertThat(page.locator("body")).matchesAriaSnapshot("" +
"- /children: deep-equal\n" +
"- list:\n" +
" - listitem:\n" +
" - listitem: \"1.1\"\n" +
" - listitem: \"1.2\"");
});
}
}
@@ -391,8 +391,8 @@ public class TestPageClock {
page.clock().install(new Clock.InstallOptions().setTime(0));
page.navigate("data:text/html,");
page.clock().pauseAt(1000);
// Internally wait to make sure the clock is paused and not running.
page.waitForTimeout(1111);
page.waitForTimeout(1000);
page.clock().resume();
int now = (int) page.evaluate("() => Date.now()");
assertTrue(now >= 0 && now <= 1000);
}
@@ -29,6 +29,7 @@ import static com.microsoft.playwright.options.ColorScheme.LIGHT;
import static com.microsoft.playwright.options.Media.PRINT;
import static com.microsoft.playwright.Utils.attachFrame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestPageEmulateMedia extends TestBase {
@Test
@@ -18,12 +18,11 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import com.microsoft.playwright.impl.PlaywrightImpl;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
@@ -99,7 +98,7 @@ public class TestPageInterception extends TestBase {
page.route("**/*", route -> {
PlaywrightException error = assertThrows(PlaywrightException.class,
() -> route.fetch(new Route.FetchOptions().setTimeout(1000)));
assertTrue(error.getMessage().contains("Timeout 1000ms exceeded"), error.getMessage());
assertTrue(error.getMessage().contains("Request timed out after 1000ms"), error.getMessage());
});
PlaywrightException error = assertThrows(PlaywrightException.class,
() -> page.navigate(server.PREFIX + "/slow", new Page.NavigateOptions().setTimeout(2000)));
@@ -142,91 +141,13 @@ public class TestPageInterception extends TestBase {
}
@Test
void shouldWorkWithGlob() {
assertTrue(globToRegex("**/*.js").matcher("https://localhost:8080/foo.js").find());
assertFalse(globToRegex("**/*.css").matcher("https://localhost:8080/foo.js").find());
assertFalse(globToRegex("*.js").matcher("https://localhost:8080/foo.js").find());
assertTrue(globToRegex("https://**/*.js").matcher("https://localhost:8080/foo.js").find());
assertTrue(globToRegex("http://localhost:8080/simple/path.js").matcher("http://localhost:8080/simple/path.js").find());
assertTrue(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/a.js").find());
assertTrue(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/b.js").find());
assertFalse(globToRegex("**/{a,b}.js").matcher("https://localhost:8080/c.js").find());
assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.jpg").find());
assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.jpeg").find());
assertTrue(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.png").find());
assertFalse(globToRegex("**/*.{png,jpg,jpeg}").matcher("https://localhost:8080/c.css").find());
assertTrue(globToRegex("foo*").matcher("foo.js").find());
assertFalse(globToRegex("foo*").matcher("foo/bar.js").find());
assertFalse(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidc/foo").find());
assertTrue(globToRegex("http://localhost:3000/signin-oidc*").matcher("http://localhost:3000/signin-oidcnice").find());
// range [] is NOT supported
assertTrue(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/v[0-9]").find());
assertFalse(globToRegex("**/api/v[0-9]").matcher("http://example.com/api/version").find());
// query params
assertTrue(globToRegex("**/api\\?param").matcher("http://example.com/api?param").find());
assertFalse(globToRegex("**/api\\?param").matcher("http://example.com/api-param").find());
assertTrue(globToRegex("**/three-columns/settings.html\\?**id=settings-**").matcher("http://mydomain:8080/blah/blah/three-columns/settings.html?id=settings-e3c58efe-02e9-44b0-97ac-dd138100cf7c&blah").find());
assertEquals("^\\?$", globToRegex("\\?").pattern());
assertEquals("^\\\\$", globToRegex("\\").pattern());
assertEquals("^\\\\$", globToRegex("\\\\").pattern());
assertEquals("^\\[$", globToRegex("\\[").pattern());
assertEquals("^\\[a-z\\]$", globToRegex("[a-z]").pattern());
assertEquals("^\\$\\^\\+\\.\\*\\(\\)\\|\\?\\{\\}\\[\\]$", globToRegex("$^+.\\*()|\\?\\{\\}\\[\\]").pattern());
assertTrue(urlMatches(null, "http://playwright.dev/", "http://playwright.dev"));
assertTrue(urlMatches(null, "http://playwright.dev/?a=b", "http://playwright.dev?a=b"));
assertTrue(urlMatches(null, "http://playwright.dev/", "h*://playwright.dev"));
assertTrue(urlMatches(null, "http://api.playwright.dev/?x=y", "http://*.playwright.dev?x=y"));
assertTrue(urlMatches(null, "http://playwright.dev/foo/bar", "**/foo/**"));
assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
// This is not supported, we treat ? as a query separator.
assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js"));
assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev"));
assertTrue(urlMatches(null, "http://playwright./?ev", "http://playwright.?ev"));
assertFalse(urlMatches(null, "http://playwright.dev/foo", "http://playwright.dev/f??"));
assertTrue(urlMatches(null, "http://playwright.dev/f??", "http://playwright.dev/f??"));
assertTrue(urlMatches(null, "http://playwright.dev/?x=y", "http://playwright.dev\\\\?x=y"));
assertTrue(urlMatches(null, "http://playwright.dev/?x=y", "http://playwright.dev/\\\\?x=y"));
assertTrue(urlMatches("http://playwright.dev/foo", "http://playwright.dev/foo?bar", "?bar"));
assertTrue(urlMatches("http://playwright.dev/foo", "http://playwright.dev/foo?bar", "\\\\?bar"));
assertTrue(urlMatches("http://first.host/", "http://second.host/foo", "**/foo"));
assertTrue(urlMatches("http://playwright.dev/", "http://localhost/", "*//localhost/"));
String[] customPrefixes = {"about", "data", "chrome", "edge", "file"};
for (String prefix : customPrefixes) {
assertTrue(urlMatches("http://playwright.dev/", prefix + ":blank", prefix + ":blank"));
assertFalse(urlMatches("http://playwright.dev/", prefix + ":blank", "http://playwright.dev/"));
assertTrue(urlMatches(null, prefix + ":blank", prefix + ":blank"));
assertTrue(urlMatches(null, prefix + ":blank", prefix + ":*"));
assertFalse(urlMatches(null, "not" + prefix + ":blank", prefix + ":*"));
}
}
Pattern globToRegex(String glob) {
return globToRegex(glob, null, false);
}
Pattern globToRegex(String glob, String baseURL, boolean webSocketUrl) {
return ((PlaywrightImpl) playwright).localUtils().globToRegex(glob, baseURL, webSocketUrl);
}
boolean urlMatches(String baseURL, String urlString, String match) {
if (match == null) {
return true;
}
String glob = (String) match;
if (glob.isEmpty()) {
return true;
}
return globToRegex(glob, baseURL, false).matcher(urlString).find();
void shouldProperlyHandleCharacterSetsInGlobs() {
page.route("**/[a-z]*.html", route -> {
APIResponse response = route.fetch(new Route.FetchOptions().setUrl(server.PREFIX + "/one-style.html"));
route.fulfill(new Route.FulfillOptions().setResponse(response));
});
Response response = page.navigate(server.PREFIX + "/empty.html");
assertEquals(200, response.status());
assertTrue(response.text().contains("one-style.css"), response.text());
}
}
@@ -16,12 +16,9 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.HttpHeader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -80,83 +77,4 @@ public class TestPageRequestContinue extends TestBase {
e.getMessage().contains("frame was detached"), e.getMessage());
assertTrue(done[0]);
}
@Test
@DisabledIf(value = "com.microsoft.playwright.TestBase#isFirefox", disabledReason = "We currently clear all headers during interception in firefox")
void continueShouldNotPropagateCookieOverrideToRedirects() throws ExecutionException, InterruptedException {
// https://github.com/microsoft/playwright/issues/35168
server.setRoute("/set-cookie", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "foo=bar;");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
page.navigate(server.PREFIX + "/set-cookie");
assertEquals("foo=bar", page.evaluate("() => document.cookie"));
server.setRedirect("/redirect", server.PREFIX + "/empty.html");
page.route("**/redirect", route -> {
Map<String, String> headers = new HashMap<>(route.request().allHeaders());
headers.put("cookie", "override");
route.resume(new Route.ResumeOptions().setHeaders(headers));
});
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
page.navigate(server.PREFIX + "/redirect");
assertEquals(asList("foo=bar"), serverRequest.get().headers.get("cookie"));
}
@Test
@DisabledIf(value = "com.microsoft.playwright.TestBase#isFirefox", disabledReason = "We currently clear all headers during interception in firefox")
void continueShouldNotOverrideCookie() throws ExecutionException, InterruptedException {
// https://github.com/microsoft/playwright/issues/35168
server.setRoute("/set-cookie", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "foo=bar;");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
page.navigate(server.PREFIX + "/set-cookie");
assertEquals("foo=bar", page.evaluate("() => document.cookie"));
page.route("**", route -> {
Map<String, String> headers = new HashMap<>(route.request().allHeaders());
headers.put("cookie", "override");
headers.put("custom", "value");
route.resume(new Route.ResumeOptions().setHeaders(headers));
});
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
// Original cookie from the browser's cookie jar should be sent.
assertEquals(asList("foo=bar"), serverRequest.get().headers.get("cookie"));
assertEquals(asList("value"), serverRequest.get().headers.get("custom"));
}
@Test
void redirectAfterContinueShouldBeAbleToDeleteCookie() throws ExecutionException, InterruptedException {
// https://github.com/microsoft/playwright/issues/35168
server.setRoute("/set-cookie", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "foo=bar;");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
page.navigate(server.PREFIX + "/set-cookie");
assertEquals("foo=bar", page.evaluate("() => document.cookie"));
server.setRoute("/delete-cookie", exchange -> {
exchange.getResponseHeaders().add("Set-Cookie", "foo=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
server.setRedirect("/redirect", "/delete-cookie");
page.route("**/redirect", route -> {
// Pass original headers explicitly when continuing.
route.resume(new Route.ResumeOptions().setHeaders(route.request().allHeaders()));
});
page.navigate(server.PREFIX + "/redirect");
Future<Server.Request> serverRequest = server.futureRequest("/empty.html");
page.navigate(server.EMPTY_PAGE);
assertNull(serverRequest.get().headers.get("cookie"));
}
}
@@ -16,7 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.options.Cookie;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
@@ -110,7 +110,7 @@ public class TestPageRoute extends TestBase {
}
@Test
void shouldNotSupportQuestionMarkInGlobPattern() {
void shouldSupportQuestionMarkInGlobPattern() {
server.setRoute("/index", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
@@ -123,18 +123,6 @@ public class TestPageRoute extends TestBase {
writer.write("index123hello");
}
});
server.setRoute("/index?hello", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("index?hello");
}
});
server.setRoute("/index1hello", exchange -> {
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("index1hello");
}
});
page.route("**/index?hello", route -> {
route.fulfill(new Route.FulfillOptions().setBody("intercepted any character"));
@@ -151,8 +139,7 @@ public class TestPageRoute extends TestBase {
assertTrue(page.content().contains("index-no-hello"), page.content());
page.navigate(server.PREFIX + "/index1hello");
assertFalse(page.content().contains("intercepted any character"), page.content());
assertTrue(page.content().contains("index1hello"), page.content());
assertTrue(page.content().contains("intercepted any character"), page.content());
page.navigate(server.PREFIX + "/index123hello");
assertTrue(page.content().contains("index123hello"), page.content());
@@ -25,7 +25,6 @@ import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestPageSelectOption extends TestBase {
@Test
@@ -239,64 +238,4 @@ public class TestPageSelectOption extends TestBase {
assertEquals(asList("blue"), page.evaluate("() => window['result'].onChange"));
}
@Test
void shouldWaitForOptionToBeEnabled() {
page.setContent(
"<select disabled>\n" +
" <option>one</option>\n" +
" <option>two</option>\n" +
"</select>\n" +
"\n" +
"<script>\n" +
"function hydrate() {\n" +
" const select = document.querySelector('select');\n" +
" select.removeAttribute('disabled');\n" +
" select.addEventListener('change', () => {\n" +
" window['result'] = select.value;\n" +
" });\n" +
"}\n" +
"</script>");
page.evaluate("() => setTimeout(hydrate, 1000)");
page.locator("select").selectOption("two");
assertEquals("two", page.evaluate("window['result']"));
assertThat(page.locator("select")).hasValue("two");
}
@Test
void shouldWaitForSelectToBeSwapped() {
page.setContent(
"<select disabled>\n" +
" <option>one</option>\n" +
" <option>two</option>\n" +
"</select>\n" +
"\n" +
"<script>\n" +
"function hydrate() {\n" +
" const select = document.querySelector('select');\n" +
" select.remove();\n" +
"\n" +
" const newSelect = document.createElement('select');\n" +
" const option1 = document.createElement('option');\n" +
" option1.textContent = 'one';\n" +
" newSelect.appendChild(option1);\n" +
" const option2 = document.createElement('option');\n" +
" option2.textContent = 'two';\n" +
" newSelect.appendChild(option2);\n" +
"\n" +
" document.body.appendChild(newSelect);\n" +
"\n" +
" newSelect.addEventListener('change', () => {\n" +
" window['result'] = newSelect.value;\n" +
" });\n" +
"}\n" +
"</script>");
page.evaluate("() => setTimeout(window.hydrate, 1000)");
page.locator("select").selectOption("two");
assertThat(page.locator("select")).hasValue("two");
assertEquals("two", page.evaluate("window['result']"));
}
}
@@ -123,7 +123,7 @@ public class TestPageSetInputFiles extends TestBase {
}
FileChooser fileChooser = page.waitForFileChooser(() -> input.click());
fileChooser.setFiles(uploadFiles.toArray(new Path[0]));
Object filesLen = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Choose File")).evaluate("e => e.files.length");
Object filesLen = page.getByRole(AriaRole.TEXTBOX).evaluate("e => e.files.length");
assertTrue(fileChooser.isMultiple());
assertEquals(filesCount, filesLen);
}
@@ -60,6 +60,13 @@ public class TestPdf extends TestBase {
assertTrue(outlineSize > noOutlineSize, "Unexpected sizes: " + outlineSize + " noOutline: " + noOutlineSize);
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
void shouldThrowInNonChromium() {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.pdf());
assertTrue(e.getMessage().contains("PDF generation is only supported for Headless Chromium"), e.getMessage());
}
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
@@ -17,10 +17,12 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.PlaywrightImpl;
import com.microsoft.playwright.impl.driver.Driver;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -23,26 +23,25 @@ import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
public class TestSelectorsRegister extends TestBase {
private static final String TAG_SELECTOR_SCRIPT = "{\n" +
" create(root, target) {\n" +
" return target.nodeName;\n" +
" },\n" +
" query(root, selector) {\n" +
" return root.querySelector(selector);\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" return Array.from(root.querySelectorAll(selector));\n" +
" }\n" +
"}";
@Test
void shouldWork() {
String selectorScript = "{\n" +
" create(root, target) {\n" +
" return target.nodeName;\n" +
" },\n" +
" query(root, selector) {\n" +
" return root.querySelector(selector);\n" +
" },\n" +
" queryAll(root, selector) {\n" +
" return Array.from(root.querySelectorAll(selector));\n" +
" }\n" +
"}";
// Register one engine before creating context.
playwright.selectors().register("tag", TAG_SELECTOR_SCRIPT);
playwright.selectors().register("tag", selectorScript);
BrowserContext context = browser.newContext();
// Register another engine after creating context.
playwright.selectors().register("tag2", TAG_SELECTOR_SCRIPT);
playwright.selectors().register("tag2", selectorScript);
Page page = context.newPage();
page.setContent("<div><span></span></div><div></div>");
@@ -135,21 +134,4 @@ public class TestSelectorsRegister extends TestBase {
});
assertTrue(e.getMessage().contains("\"css\" is a predefined selector engine"));
}
@Test
void shouldThrowAlreadyRegisteredErrorWhenRegistering() {
// https://github.com/microsoft/playwright/issues/36467
// this test is about the exception *before* there's a context created
context.close();
// Register the selector engine first
playwright.selectors().register("alreadyRegistered", TAG_SELECTOR_SCRIPT);
// Attempt to register the same selector engine again should throw an error
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
playwright.selectors().register("alreadyRegistered", TAG_SELECTOR_SCRIPT);
});
assertTrue(e.getMessage().contains("\"alreadyRegistered\" selector engine has been already registered"));
}
}
@@ -18,7 +18,6 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.AriaRole;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import java.util.regex.Pattern;
@@ -242,66 +241,6 @@ public class TestSelectorsRole extends TestBase {
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setDisabled(false)).evaluateAll("els => els.map(e => e.outerHTML)"));
}
@Test
void shouldInheritDisabledFromTheAncestor() {
page.setContent(
"<span aria-disabled=\"true\">\n" +
" <button>Click me!</button>\n" +
"</span>");
assertThat(page.locator("button")).isDisabled();
page.setContent(
"<span aria-disabled=\"true\">\n" +
" <h1>Heading</h1>\n" +
"</span>");
// Non-control roles do not inherit disabled state
assertThat(page.locator("h1")).isEnabled();
}
@Test
void shouldSupportDisabledFieldset() {
page.setContent(
"<fieldset disabled>\n" +
" <input></input>\n" +
" <button data-testid=\"inside-fieldset-element\">x</button>\n" +
" <legend>\n" +
" <button data-testid=\"inside-legend-element\">legend</button>\n" +
" </legend>\n" +
"</fieldset>\n" +
"\n" +
"<fieldset disabled>\n" +
" <legend>\n" +
" <div>\n" +
" <button data-testid=\"nested-inside-legend-element\">x</button>\n" +
" </div>\n" +
" </legend>\n" +
"</fieldset>\n" +
"\n" +
"<fieldset disabled>\n" +
" <div></div>\n" +
" <legend>\n" +
" <button data-testid=\"first-legend-element\">x</button>\n" +
" </legend>\n" +
" <legend>\n" +
" <button data-testid=\"second-legend-element\">x</button>\n" +
" </legend>\n" +
"</fieldset>\n" +
"\n" +
"<fieldset disabled>\n" +
" <fieldset>\n" +
" <button data-testid=\"deep-button\">x</button>\n" +
" </fieldset>\n" +
"</fieldset>");
assertThat(page.getByTestId("inside-legend-element")).isEnabled();
assertThat(page.getByTestId("nested-inside-legend-element")).isEnabled();
assertThat(page.getByTestId("first-legend-element")).isEnabled();
// Only the first legend is exempt from disabled fieldset
assertThat(page.getByTestId("second-legend-element")).isDisabled();
// Nested fieldsets inherit disabled state
assertThat(page.getByTestId("deep-button")).isDisabled();
}
@Test
void shouldSupportLevel() {
page.setContent("<h1>Hello</h1>\n" +
@@ -418,6 +357,15 @@ public class TestSelectorsRole extends TestBase {
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("^H[ae]llo$"))).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.locator("role=button[name=/h.*o/i]").evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hallo\"></div>"
), page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("h.*o", Pattern.CASE_INSENSITIVE))).evaluateAll("els => els.map(e => e.outerHTML)"));
assertEquals(asList(
"<div role=\"button\" aria-label=\" Hello \"></div>",
"<div role=\"button\" aria-label=\"Hello\" aria-hidden=\"true\"></div>"
@@ -17,7 +17,7 @@
package com.microsoft.playwright;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.google.gson.JsonObject;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.Location;
import com.microsoft.playwright.options.MouseButton;
@@ -182,7 +182,7 @@ public class TestTracing extends TestBase {
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<TraceEvent> groups = events.stream().filter(e -> "tracingGroup".equals(e.method)).collect(Collectors.toList());
assertEquals(1, groups.size());
assertEquals("actual", groups.get(0).title);
assertEquals("actual", groups.get(0).apiName);
}
@@ -203,8 +203,8 @@ public class TestTracing extends TestBase {
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle()).collect(Collectors.toList());
assertEquals(asList("outer group", "Frame.goto", "inner group 1", "Frame.click", "inner group 2", "Frame.isVisible"), calls);
List<String> calls = events.stream().filter(e -> e.apiName != null).map(e -> e.apiName).collect(Collectors.toList());
assertEquals(asList("outer group", "Page.navigate", "inner group 1", "Frame.click", "inner group 2", "Page.isVisible"), calls);
}
@Test
@@ -241,30 +241,30 @@ public class TestTracing extends TestBase {
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
List<String> calls = events.stream().filter(e -> e.apiName != null).map(e -> e.apiName)
.collect(Collectors.toList());
assertEquals(asList(
"BrowserContext.clockInstall",
"Frame.setContent",
"Clock.install",
"Page.setContent",
"Frame.click",
"Frame.click",
"Page.keyboardType",
"Page.keyboardPress",
"Page.keyboardDown",
"Page.keyboardInsertText",
"Page.keyboardUp",
"Page.mouseMove",
"Page.mouseDown",
"Page.mouseMove",
"Page.mouseWheel",
"Page.mouseUp",
"BrowserContext.clockFastForward",
"BrowserContext.clockFastForward",
"BrowserContext.clockPauseAt",
"BrowserContext.clockRunFor",
"BrowserContext.clockSetFixedTime",
"BrowserContext.clockSetSystemTime",
"BrowserContext.clockResume",
"Keyboard.type",
"Keyboard.press",
"Keyboard.down",
"Keyboard.insertText",
"Keyboard.up",
"Mouse.move",
"Mouse.down",
"Mouse.move",
"Mouse.wheel",
"Mouse.up",
"Clock.fastForward",
"Clock.fastForward",
"Clock.pauseAt",
"Clock.runFor",
"Clock.setFixedTime",
"Clock.setSystemTime",
"Clock.resume",
"Frame.click"),
calls);
}
@@ -285,31 +285,19 @@ public class TestTracing extends TestBase {
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
List<String> calls = events.stream().filter(e -> e.apiName != null).map(e -> e.apiName)
.collect(Collectors.toList());
assertEquals(asList("Frame.goto"), calls);
assertEquals(asList("Page.navigate"), calls);
}
private static class TraceEvent {
String type;
String name;
String title;
@SerializedName("class")
String clazz;
String apiName;
String method;
Double startTime;
Double endTime;
String callId;
String renderedTitle() {
if (title != null) {
return title;
}
if (clazz != null && method != null) {
return clazz + "." + method;
}
return null;
}
}
private static List<TraceEvent> parseTraceEvents(Path traceFile) throws IOException {
@@ -1,16 +1,66 @@
# Client Certificate test-certificates
Regenerate all certificates by running:
## Server
```bash
openssl req \
-x509 \
-newkey rsa:4096 \
-keyout server/server_key.pem \
-out server/server_cert.pem \
-nodes \
-days 365 \
-subj "/CN=localhost/O=Client\ Certificate\ Demo" \
-addext "subjectAltName=DNS:localhost,DNS:local.playwright"
```
## Trusted client-certificate (server signed/valid)
```
bash generate.sh
mkdir -p client/trusted
# generate server-signed (valid) certifcate
openssl req \
-newkey rsa:4096 \
-keyout client/trusted/key.pem \
-out client/trusted/csr.pem \
-nodes \
-days 365 \
-subj "/CN=Alice"
# sign with server_cert.pem
openssl x509 \
-req \
-in client/trusted/csr.pem \
-CA server/server_cert.pem \
-CAkey server/server_key.pem \
-out client/trusted/cert.pem \
-set_serial 01 \
-days 365
```
## Self-signed certificate (invalid)
```
mkdir -p client/self-signed
openssl req \
-newkey rsa:4096 \
-keyout client/self-signed/key.pem \
-out client/self-signed/csr.pem \
-nodes \
-days 365 \
-subj "/CN=Bob"
# sign with self-signed/key.pem
openssl x509 \
-req \
-in client/self-signed/csr.pem \
-signkey client/self-signed/key.pem \
-out client/self-signed/cert.pem \
-days 365
```
## Java: Convert PEM Files to PKCS12
Java server understands only PKCS12 keys, after copying the certificates from Node.js Playwright we need to convert them.
```
bash generate_java.sh
openssl pkcs12 -export -in server_cert.pem -inkey server_key.pem -out server_keystore.p12 -name myalias
```
@@ -1,28 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEyzCCArOgAwIBAgIUUo60oaPj20QM6oeGSn+2CT5j7GYwDQYJKoZIhvcNAQEL
BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI1MDcyMTE1NTYyMFoXDTM1MDcxOTE1NTYy
MIIEyzCCArOgAwIBAgIUYps4gh4MqFYg8zqQhHYL7zYfbLkwDQYJKoZIhvcNAQEL
BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI0MDcxOTEyNDc0MFoXDTI1MDcxOTEyNDc0
MFowDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAzQMXYOZz3ILrbF9qpDng2pw2wJf1UFopehwaYyu7riWJ9+ADrhSnCSFBM3Sc
MBc/8dIR6etWwci8QwJ/MtvIU0yx4llq+53G+19Bc1teC6q/b4QCRDIcTGxOZoR+
jfYZVjPODEyJ5y5MZIo34ZP4bu+JnpT4W7+uojm3jOoyNPqXMcc70uAhfSKG+Jfr
wZKteA4T5VKFytVcWgh4v03z8zTYeW3kD4lCsongBz6yu2dn3D6XMROnxiPwi+IR
QqX1pwnJ0UA3CTeOHEw1jA3koxWIIg44PWaPaCj9Udrhf4ew00XLPWVZP8T5rVf8
yUfecWQCR7FueJFqoLhPMMFi17rYmGZUvw3/YkXBjay4Q9e+G2WS3Xk8u+I1sCuV
BJNBRv9DqtMC9D/N9NI8GkLrXwZmk82SXG+cQ0TSkNUHYI/03YKoqsn5H8PsG7Tc
+Y2Ca6TaCWims7lvOg7U0E6lu2h5NGcdWHFPJ9qfe+xho/yfYYwGqEKanGAu1kd5
SbIaX6/YiM5/Pp/96MeRNrB9kLzDnZTNuGtdCawVFgbkWmfX2Z6/a0d6SvZGDzBx
xTVZRB0my01E1FP53MS8YRH98HUjGEwNRVq2e1W+aXldKppgZ85GZD3l2YTeuI0i
PJCDUzQiaWzFtWc8s13YQ1HLCOyOXF7QqMyNCiLGb4xQ56kCAwEAAaMhMB8wHQYD
VR0OBBYEFMqdCDxkZm8HxNI4MLLveAVTdH7UMA0GCSqGSIb3DQEBCwUAA4ICAQAB
MKd3WEJ8zI51FuyeTcMq6L1zk2vmKTFg6T7HZJhNZoD1AYvvsKJ8mrqeSwhxqjlE
0H2FGLY+Z8Fw3+TE1QuvTuz3gwRI+yzBEyqi/fEGCGrhOVcWaXGgLCtWG+BA4Su8
HenK97/n3OnXUnBozRPZMH02IaLrOiEGbpaabXKCCabpm5U1oGq437e3SeAeIL7U
WxuHhHBx0yo9j7ACaCL6mz8xpk8NaRZpPy22MTlrPKwbOK2eYf3Jy5fHa/f6edTs
KqZewI7t4oe/OqKdjyTgGYkjTE3Xcmo2T/fmcAeEP3HJX267kCzBi5J3McwonWxD
N8zz9qKSf5YGQy140eEOTjjESwlPz6zfrTW92YdCIr63k9UCDL2HGQTRSxB6g4BQ
loVzKS9/BKhulGqvSGvEoj6D+qG/PgFlBtJoE71X+vSIxvdbnOVmOi/l+NGNuP1Z
nwnDtZWp4BKshhSKvqeOI+EyNMQ4FL20S4w8T+LR873jKrbd2MEuAsJiygWh+/ZJ
haHTEhFxvH/a4i8gb/SGZlFB6oyPJ+XM5kZo4fcp7PnzxhYrIaEpPq+AR/3657hm
AajXpS5lTCkNJc85QeHHj/0geDsOvfK4XUj2lgaJ0gXpgsoxnSCSq6Xox6ZqAVON
0ra1KkGBTQH+5DxJ2Gp1UBaucrLYZTfXuJ8fPYeeNQ==
AgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzOv9TDlB33Unov
jch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfNbmS8PWbnQ4ds
9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKziANUo8h8t0dm
TX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX2LrIUHGy+Eux
nJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38GwKVOyy1msRL
toGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+ccBXiSQEe7BA
kdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+UMqeGaYCpkHr
TiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0mUpL8+yp7mfA
7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMkkOBMLHWJTefd
6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9+12iGbKvwJ2e
nJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEAAaMhMB8wHQYD
VR0OBBYEFPxKWTFQJSg4HD2qjxL0dnXX/z4qMA0GCSqGSIb3DQEBCwUAA4ICAQBz
4H1d5eGRU9bekUvi7LbZ5CP/I6w6PL/9AlXqO3BZKxplK7fYGHd3uqyDorJEsvjV
hxwvFlEnS0JIU3nRzhJU/h4Yaivf1WLRFwGZ4TPBjX9KFU27exFWD3rppazkWybJ
i4WuEdP3TJMdKLcNTtXWUDroDOgPlS66u6oZ+mUyUROil+B+fgQgVDhjRc5fvRgZ
Lng8wuejCo3ExQyxkwn2G5guyIimgHmOQghPtLO5xlc67Z4GPUZ1m4tC+BCiFO4D
YIXl3QiIpmU7Pss39LLKMGXXAgLRqyMzqE52lsznu18v5vDLfTaRH4u/wjzULhXz
SrV1IUJmhgEXta4EeDmPH0itgKtkbwjgCOD7drrFrJq/EnvIaJ5cpxiI1pFmYD8g
VVD7/KT/CyT1Uz1dI8QaP/JX8XEgtMJaSkPfjPErIViN9rh9ECCNLgFyv7Y0Plar
A6YlvdyV1Rta/BHndf5Hqz9QWNhbFCMQRGVQNEcoKwpFyjAE9SXoKJvFIK/w5WXu
qKzIYA26QXE3p734Xu1n8QiFJIyltVHbyUlD0k06194t5a2WK+/eDeReIsk0QOI8
FGqhyPZ7YjR5tSZTmgljtViqBO5AA23QOVFqtjOUrjXP5pTbPJel99Z/FTkqSwvB
Rt4OX7HfuokWQDTT0TMn5jVtJyi54cH7f9MmsNJ23g==
-----END CERTIFICATE-----
@@ -1,26 +1,26 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEUzCCAjsCAQAwDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOC
Ag8AMIICCgKCAgEAzQMXYOZz3ILrbF9qpDng2pw2wJf1UFopehwaYyu7riWJ9+AD
rhSnCSFBM3ScMBc/8dIR6etWwci8QwJ/MtvIU0yx4llq+53G+19Bc1teC6q/b4QC
RDIcTGxOZoR+jfYZVjPODEyJ5y5MZIo34ZP4bu+JnpT4W7+uojm3jOoyNPqXMcc7
0uAhfSKG+JfrwZKteA4T5VKFytVcWgh4v03z8zTYeW3kD4lCsongBz6yu2dn3D6X
MROnxiPwi+IRQqX1pwnJ0UA3CTeOHEw1jA3koxWIIg44PWaPaCj9Udrhf4ew00XL
PWVZP8T5rVf8yUfecWQCR7FueJFqoLhPMMFi17rYmGZUvw3/YkXBjay4Q9e+G2WS
3Xk8u+I1sCuVBJNBRv9DqtMC9D/N9NI8GkLrXwZmk82SXG+cQ0TSkNUHYI/03YKo
qsn5H8PsG7Tc+Y2Ca6TaCWims7lvOg7U0E6lu2h5NGcdWHFPJ9qfe+xho/yfYYwG
qEKanGAu1kd5SbIaX6/YiM5/Pp/96MeRNrB9kLzDnZTNuGtdCawVFgbkWmfX2Z6/
a0d6SvZGDzBxxTVZRB0my01E1FP53MS8YRH98HUjGEwNRVq2e1W+aXldKppgZ85G
ZD3l2YTeuI0iPJCDUzQiaWzFtWc8s13YQ1HLCOyOXF7QqMyNCiLGb4xQ56kCAwEA
AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQAn/ZI7IkBUEfhZHefwtF+QHCyxSEKvqwHq
fSqKVdarBPz8Ik8m3icj8R/DcS3y5jgzx3x8bXQoDpgsAQgeb825NRv2wAQAGoH1
8vh204lTyjqzrgtK7eQeQDc7fjeigIkxQsAK9zk4BaFUWp0wEC0RLVAgvlQTl7vu
n1jSSrhK8tvGy62/cIxZfwD0bAMHlW4m1A4fUuSGWQX2KldgA8tnmT6wx0If/nKb
VB68AMbyMHUeb32v9wEvx2nHlwMjqNFeg7vYyJXOfBdDILUl+OTBoQY1X+jSx5iM
txTzmA8Hcgx0Fq+BnbQuZCLqFpNWEfenAtQtaAFuJwMiKCf6kgbqkDVShJkmt+vC
j3dcsVMZDsdMk4qRpiJhaTQOYmsMGCj4uoDpFGjwPoUwlDkjYgHAAsm9uCkshc+m
WZO7I6Z3Tbi3XskJvAMc3dTWjtc6nApEtr/mn8LcETfOp7RRSfjllj6ijWUrVwUy
BpzU9C/zLTkhFX0DVDCIV+jEefF8JPfzSKLgXyRbInTz1/6/sKXtswXW0NjzqLMI
C9ggMBhOiDv9KJn3G/mY4CqIfo9KMzF+++4t+wdXTir8DWNlMUAn1vlBwxZAgKCM
GonVExBU0VIGCpyTRLkesEHnPMgybP6gLzP3++54x288OS5JwuPPtkDcsBHUjTq8
HxTJvUul/Q==
Ag8AMIICCgKCAgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzO
v9TDlB33Unovjch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfN
bmS8PWbnQ4ds9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKz
iANUo8h8t0dmTX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX
2LrIUHGy+EuxnJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38
GwKVOyy1msRLtoGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+
ccBXiSQEe7BAkdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+
UMqeGaYCpkHrTiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0
mUpL8+yp7mfA7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMk
kOBMLHWJTefd6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9
+12iGbKvwJ2enJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEA
AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCb07d2IjUy1PeHCj/2k/z9FrZSo6K3c8y6
b/u/MZ0AXPKLPDSo7UYpOJ8Z2cBiJ8jQapjTSEL8POUYqcvCmP55R6u68KmvINHo
+Ly7pP+xPrbA4Q0WmPnz37hQn+I1he0GuEQyjZZqUln9zwp67TsWNKxKtCH+1j8M
Ltzx6kuHCdPtDUtv291yhVRqvbjiDs+gzdQYNJtAkUbHwHFxu8oZhg8QZGyXYMN8
TGoQ1LTezFZXJtX69K7WnrDGrjsgB6EMvwkqAFSYNH0LFvI0xo13OOgXr9mrwohA
76uZtjXL9B15EqrMce6mdUZi46QJuQ2avTi57Lz+fqvsBYdQO89VcFSmqu2nfspN
QZDrooyjHrlls8MpoBd8fde9oT4uA4/d9SJtuHUnjgGN7Qr7eTruWXL8wVMwFnvL
igWE4detO9y2gpRLq6uEqzWYMGtN9PXJCGU8C8m9E2EBUKMrT/bpNbboatLcgRrW
acj0BRVqoVzk1sRq7Sa6ejywqgARvIhTehg6DqdMdcENCPQ7rxDRu5PSDM8/mwIj
0KYl8d2PlECB4ofRyLcy17BZzjP6hSnkGzcFk0/bChZOSIRnwvKbvfXnB45hhPk8
XwT/6UNSwC2STP3gtOmLqrWj+OE0gy0AkDMvP3UnQVGMUvgfYg+N4ROCVtlqzxe9
W65c05Mm1g==
-----END CERTIFICATE REQUEST-----
@@ -1,52 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDNAxdg5nPcguts
X2qkOeDanDbAl/VQWil6HBpjK7uuJYn34AOuFKcJIUEzdJwwFz/x0hHp61bByLxD
An8y28hTTLHiWWr7ncb7X0FzW14Lqr9vhAJEMhxMbE5mhH6N9hlWM84MTInnLkxk
ijfhk/hu74melPhbv66iObeM6jI0+pcxxzvS4CF9Iob4l+vBkq14DhPlUoXK1Vxa
CHi/TfPzNNh5beQPiUKyieAHPrK7Z2fcPpcxE6fGI/CL4hFCpfWnCcnRQDcJN44c
TDWMDeSjFYgiDjg9Zo9oKP1R2uF/h7DTRcs9ZVk/xPmtV/zJR95xZAJHsW54kWqg
uE8wwWLXutiYZlS/Df9iRcGNrLhD174bZZLdeTy74jWwK5UEk0FG/0Oq0wL0P830
0jwaQutfBmaTzZJcb5xDRNKQ1Qdgj/Tdgqiqyfkfw+wbtNz5jYJrpNoJaKazuW86
DtTQTqW7aHk0Zx1YcU8n2p977GGj/J9hjAaoQpqcYC7WR3lJshpfr9iIzn8+n/3o
x5E2sH2QvMOdlM24a10JrBUWBuRaZ9fZnr9rR3pK9kYPMHHFNVlEHSbLTUTUU/nc
xLxhEf3wdSMYTA1FWrZ7Vb5peV0qmmBnzkZkPeXZhN64jSI8kINTNCJpbMW1Zzyz
XdhDUcsI7I5cXtCozI0KIsZvjFDnqQIDAQABAoICAAN6aFLBqijNNFEM/95MKJVQ
5eln0pbDxtUeZbC1yNv8IU56J5nUGh7gqG5m7bDvrgssXxcuwdStEwuYft+2JJyM
Li7qyTK+YyY34CCExfBQ++k4jkDJsFr4Ee7xk8OVD6o7nATvpf3M9mkUwryyIdqA
+B7fhGSrGHuCWuu6O/KT502GBazu1kadF7jfO/XXZxfEtl/zQdeWfdf9sY2+VPOU
+5C41XARijcE+Y7p6IafKx8MlUxU+ulUygOXiOcucV/dfcXt7tkaTxAKF3T6Nd0x
8/Ku9tOM2kVAP8b8HYwIOW7mLdvrbKOVNA61sdFY5axbD+JXP2pufiZ+pgJL36FF
SDQIW5M3aH7CSa1i3i4MP49jWomhTNwseVrXsDuGCKVqgIR5LZwpS4VOHLAILkCh
cIEDnoMS9YPuQdENIIxKyZGGHaeJ+LRb4w+szvtmu55Kp+N7AubPfoypPrx7a8LN
8/0/w731DS6nTICYXXzzGoB3cefb3nsBNaH1+edffPTZOYlFZ9ElFjIs/xvWCSy4
qYwQ1cW4DslIiVD62wm8Df2yr/5J6znfU01RXQ4GWfmDNFBdYsQO/8JEy6UZEvCy
tFZ1gseD9K69O4XZSEKRKIvv8+1Y/CwD0ppIOYCIycTKn87GXFmsjbuj8tghmHp4
TUi3EUvrw8mQMi5QBa5BAoIBAQD8JzdNy4ietoT8UUWqBT+ZSURPWeQN1esbncYU
b9viBIznnjjFFr5JdYa5k3rxM+bTRq47NRt+r0HOvyJWUFJcI6tbgmGtW+rmM2kB
hq6ekTJleLz+/cjNSjWD14avNORPz/ozZMlcz52NEl31pdniuDbueeNgyh+CkJtH
BS8s8mMVZ+3NtafZ1ilGn/RP+n21C1J8Kcxd/1srtfcpybzAQiSYul2DlKVPGvqO
XpLyt42/cyc7a3MyXtms2XhJ62fDK24Qptp6KJNTzqdtY+KP/iOW0SUgxf+JpC87
W2NJW7tqyyaebn7KO1lGs9y03KzwaLZvy2RaBfjQnxS8uXzpAoIBAQDQI8QHNtr5
nHYSzLZJMhJkP641wQWo4ODfkWxtEMqTyOXrVw3L8HMdA03Nmf9jnCv5awYYA3j0
PmSL3PdM36d3VsAyyxMBN4HrH2Z94oTnoKmDfXjB3prhPYgO6aosSvE8rw1N055o
757p9vAA5w9apBBLNdcm3cjUm2ZKeocL4wnFjOW63CtcFEouE4R7C32rauEu9Bdg
dAXciBmOrHtihJUQrpMfyfN2fVSLbO4SoFy7ZHq5YKFk4MzNIp/cENwRIqdcLvOz
o++RSbwptRtkd/HZCFSh/4gEPRLe/k5gGErS9ZqpeSMfV++IdBMqC0Sx3UpyXuue
FOhIvnLpJlzBAoIBAGjcDh2mBLyr/oXHbocUA6zFUUkGgtZWHZ2wcQ1Sr0hAyDAS
Fl2v5ZY677oA4OGpydYW0KICpdp7G4zU43ytjnKOytYVVHV5gigVPRfLYJbEnwaf
vUj1VSo6MCMR4ArAnimqvcvdn/eex1BBUR20yPWF0iI+QhagN5ZeeJSCTWoNqrLe
M4CWiKUIcMXUAw+3hctiV/0WjMySQuHcnFqecIYre3igF/9+M3jAKW5HWijhuGrj
gm8tcgyCcVd2YJWs9cuuJel62eRvN0Vk7S+KmE91SmuPsjb84BXnV1UB3jpFkZ0J
upesL8H+CFRku+Xi13Bqu2OmW6csUJrBbShGovECggEBAKGk9SupJXyvT1+gTn0f
/vqOHiyvAEc8hkf6t5sobDtDzZPs4tEcpznEBBuF2rqwYdJtlKj3oWsGPa4FaKXy
GCvtWozX+6V5R1Oj6kQftJnyw1NUEYF28Q+2asEyJTAK77jyNkHX9HGIjwEi/xek
Wt9JBUJzyOjtW3gKS/HRoKnRpBghKZTqQl5bf5SzIbMxpGKJOeLuPG1zDc5MgJS2
TYigcOgovCf2/jZqdUtmyKn8kqgSC+GGMzGWCFfT6RTOnypLoHBOIoPD8F0ER7aY
aXKoWFH2T0wUmLy59brrA1FL7GhTx86QPn+sGmH9y5hecfY0ZwnVv+TgVdmQ1stN
OMECggEBAOXDX319Dmo0ydAPYyngK4/slOetGaJmz8looU8a2R2+Ko/VMTZDmJhf
P0vS74g3U7sukRjmYzUY1mPj27CDURvk1ENam9KPOQ59ws/TaHaJ7tobjUXVQ93/
OREkrlCuqbEqJJzQ01mCWIbmDnGvJwD87rW6YwmI9Rs+kjZxNLj+IW4CqpY36q9A
HwaUZLXc2q0W1CqLmFYF5HotvSFIAYHWuClEmM2NI9+0VItarBz6AwCnVXwKbJLC
irXlllX+63uloTDR5W1ymy2hTUrhE1jgh9DR4106QSVDiEWqme/BAWmUoyuN/zms
v3/WVVAXEcIowL3T4jzJ0RLdf1qE8Bc=
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDXv15OypxzVzcA
5AfMJkTJgs+1rYIICxxQWpwRn29a+NbS7M6/1MOUHfdSei+NyHgISVk4GHNNp1Wx
uadgqnHDJVTz1YK5Aq953PiTW+7tLVivJ81uZLw9ZudDh2z00HtKAk0dkgu/H0Ne
5Z4UTLNEijX3zjPgeF4HOuR/v3UuqlgporOIA1SjyHy3R2ZNf9ug7PxwSdA3C1ML
RlfdoPShsb9QCGv/bZkY+j8Trn1+Him3JhfYushQcbL4S7Gcn1jhxOprzwR7oroC
QOJP8Cg1A3EThf76Oou6J7yP9likjU6WXfwbApU7LLWaxEu2gaYhI3CQwhAMYAGs
mXGCk/hBABD7Ty/2yvNc6WR3F2vs4I/zWv5xwFeJJAR7sECR0nyX3yXl2msn74Yn
5JlxXj7+IZHR0pTYh0AbkeIpkgWfpyH1TH5Qyp4ZpgKmQetOKJ60fBxeC1UGUTSM
WYH8eymYj87Rpsr6Csya55ofte1MjxgefDSZSkvz7KnuZ8DvMW6yAWDKIE9d4P81
CCDy+NrruT753VUahebbGv7lY9AJJuBCgySQ4EwsdYlN593oXhnkz6gjRXBht/p5
BbbjrAmCkIdI9HRV2KN3owAZFYpS4t8OwD37XaIZsq/AnZ6cmVK1+3ZXYtlyMF90
gxuKBbVpJU03nDqbphWtA+vLdY+RZwIDAQABAoICAETxu6J0LuDQ+xvGwxMjG5JF
wjitlMMbQdYPzpX3HC+3G3dWA4/b3xAjL1jlAPNPH8SOI/vAHICxO7pKuMk0Tpxs
/qPZFCgpSogn7CuzEjwq5I88qfJgMKNyke7LhS8KvItfBuOvOx+9Ttsxh323MQZz
IGHrPDq8XFf1IvYL6deaygesHbEWV2Lre6daIsAbXsUjVlxPykD81nHg7c0+VU6i
rZ9WwaRjkqwftC6G8UVvQCdt/erdbYv/eZDNJ5oEdfPX6I3BHw6fZs+3ilq/RSoD
yovRozS1ptc7QY/DynnzSizVJe4/ug6p7/LgTc2pyrwGRj+MNHKv73kHo/V1cbxF
fBJCpxlfcGcEP27BkENiTKyRQEF1bjStw+UUKygrRXLm3MDtAVX8TrDERta4LAeW
XvPiJbSOwWk2yYCs62RyKl+T1no7alIvc6SUy8rvKKm+AihjaTsxTeACC1cBc41m
5HMz1dqdUWcB5jbnPsV+27dNK1/zIC+e0OXtoSXvS+IbQXo/awHJyXv5ClgldbB9
hESFTYz/uI6ftuTM6coHQfASLgmnq0fOd1gyqO6Jr9ZSvxcPNheGpyzN3I3o5i2j
LTYJdX3AoI5rQ5d7/GS2qIwWf0q8rxQnq1/34ABWD0umSa9tenCXkl7FIB4drwPB
4n7n+SL7rhmv0vFKIjepAoIBAQD19MuggpKRHicmNH2EzPOyahttuhnB7Le7j6FC
afuYUBFNcxww+L34GMRhmQZrGIYmuQ3QV4RjYh2bowEEX+F5R1V90iBtYQL1P73a
jYtTfaJn0t62EBSC//w2rtaRJPgGhbXbnyid64J0ujRFCelej8FRJdBV342ctRAL
0RazxQ/KcTRl9pncALxGhnSsBElZlDtZd/dWnWBDZ/fg/C97VV9ZQLcpyGvL516i
GpB8BQsHiIe9Jt5flZvcKB7z/KItGzPB4WK6dpV8t/FeQiUpZXkQlqO03XaZT4NP
AEGH3rKIRMpP7TORYFhbYrZwov3kzLaggax2wGPTkfMFNlTjAoIBAQDgjsYfShkz
6Dl1UTYBrDMy9pakJbC6qmd0KOKX+4XH/Dc1mOzR8NGgoY7xWXFUlozgntKKnJda
M6GfOt/dxc0Sq7moYzA7Jv4+9hNdU3jX5YrqAbcaSFj6k4yauO2BKCBahQo8qseY
a3N5f0gp+5ftTMvOTwGw3JRJFJq0/DvKWAYLIaJ0Oo77zGs0vxa1Aqob10MloXt5
DMwjazWujntTzTJY1vsfsBHa8OEObMwiftqnmn6L4Qprd3AzQkaNlZEsvERyLfFq
1pu4EsDJJGdVfpZYfo+6vTglLXFBLEUQmh4/018Mw4O4pGgCVMj/wict/gTViQGC
qSj+IOThsTytAoIBAHu3L3nEU/8EwMJ54q0a/nW+458U3gHqlRyWCZJDhxc9Jwbj
IMoNRFj39Ef3VgAmrMvrh2RFsUTgRG5V1pwhsmNzmzAXstHx2zALaO73BZ7wcfFx
Yy8G9ZpTMsU6upj1lICLX0diTmbo4IzgYIxdiPJUsvOjZqDbOvsZJEIdYSL5u5Cj
0qx7FzdPc2SyGxuvaEnTwuqk6le5/4LIWCnmD+gksDpP0BIHSxmcfsBhRk3rp3mZ
llVxqKdBtM1PrQojCFxR833RZfzOyzCZwaIc+V5SOUw7yYqfXxmMokrpoQy72ueq
Wm1LrgWxBaCqDYSop7cftbkUoPB2o3/3SNtVUesCggEAReqOKy3R/QRf53QaoZiw
9DwsmP0XMndd8J/ONU3d0G9p7SkpCxC05BOJQwH7NEAPqtwoZ3nr8ezDdKVLEGzG
tfp7ur7vRGuWm5nYW6Viqa3Re5x/GxLNiW8pRv8vC5inwidMEamGraE++eQ0XsXz
/rF7f0fAGgYDsWFV7eXe49hWQV7+iru0yxdRhcG9WyxyNGrogC3wGLdwU9LMiwXX
xjbMZzbAR5R1arq3B9u+Dzt57tc+cWTm7qDocT1AZFLeOZSApyBA22foYf6MwdOw
zMC2JOV68MR7V6/3ZDhZZJrnsi2omXvCZlnh/F/TmTYlJr/BV47pxnnOxpkNSmv5
nQKCAQBRqrsUVO7NOgR1sVX7YDaekQiJKS6Vq/7y2gR4FoLm/MMzNZQgGo9afmKg
F2hSv6tuoqc33Wm0FnoSEMaI8ky0qgA5kwXvhfQ6pDf/2zASFBwjwhTyJziDlhum
iwWe1F7lNaVNpxAXzJBaBTWvHznuM42cGv5bbPBSRuIRniGsyn/zYMrISWgL+h/Q
fsQ2rfPSqollPw+IUPN0mX+1zg6PFxaR4HM9UrRX7cnRKG20GIDPodsUl8IMg+SO
M5YG/UqDD10hfeEutvQIvl0oJraBWT34cqUZLVpUwJzf1be7zl9MzHGcym/ni7lX
dg6m3MAyZ1IXjHlogOdmGvnq07/w
-----END PRIVATE KEY-----
@@ -1,29 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFAzCCAuugAwIBAgIBATANBgkqhkiG9w0BAQsFADA2MRIwEAYDVQQDDAlsb2Nh
bGhvc3QxIDAeBgNVBAoMF0NsaWVudCBDZXJ0aWZpY2F0ZSBEZW1vMB4XDTI1MDcy
MTE1NTYxOFoXDTM1MDcxOTE1NTYxOFowEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4ggn74iAHLgOWOiOvB2CPe+Hr7W6S
TYJLZOoqPdh7mv7QGm8cYxfD+26p13aEaW4/qn45losWdEPPy2ZiVIF+kcOP0R4A
qsB0w9UHT4WSzCWtDqs8ywDMJ6tHge0++8S1bTpdutn/m8DPnKtkD9RQUzLFmGDO
+mB08Xu+egTzJvURbHXRJ27E+CXUXLEHbAJd8EKjJiQYXhcj4lzUXOUg+xkpPAGe
1dgQ6BDkv3xq/81S9NTIT5YriHEm6egi9AFJLZbbZtCpRQm1MDMq7zfD7oL6ECLG
rJ/aaxLEM49gMdw2SscYHotVxX2OMAKgN/ytB18L/mIQ4pOWp3HJQ+nks9Zu1V8c
g7Pz5pWwjqoncbyUaBi3vGsitAot3cyLXbN9hH8zQB8QqGyNLvqQnCJOBlYxOPA8
M8NQGDThWKspcix0LhkmnYsWWt3+KEGEpFRcJCEthm/DAd1GImP7/dg1/btT/zOs
IIkIoCyBwznuR4M4XoNTbIBsiTnO/6PjOdTrjLVX7vMueTjdbCgK3VeOuwetbZ7a
Z/hvMJCjJ4wuM3l2ZyfEQlP4gJLJS3Fw2PRsySZJl9JyEzwTqYVosDF5SF8uw0HI
cFaliZCqBbgYfOiQzjIX/GiHZdor1ZrhAbO/MpSxuxGzxF7Em4n+4p816NnC9S2J
bHRetkM9P1adHwIDAQABo0IwQDAdBgNVHQ4EFgQUXhGloOgfXb0tY7HZZGil/d11
bcwwHwYDVR0jBBgwFoAU8doR+Mmgh+1KQdNvybCZ7bsu2J8wDQYJKoZIhvcNAQEL
BQADggIBADNVu8YodDjB0474ztGSjV8WO0x984WQpGG6VPIt9xnnswNM8aOZVMNC
AS12BjgviutSBUbL40xYNlytnqP0KtlgZSNpPVTGMAOoGttbgV7w46+hyBgV4hYs
PsYrxMU1/4hTW/aGnNXpWJ4VwOESknSqUudzfy1mNVaEuPoL97SCvPrlKPU6El1a
Wmove/QTKsbsjWaahqE59uClQ6CBcWbxpN5CLyIVM4c/UrsoXdwRewgXl19YFRzR
l6LXBPid4UPQqdE6XIxgsNpoTDwAyxSRMPydlulJD5sSTaXHB7h63DsT9mEydjR3
jPjhNZL5ADtDes3/UhfpXDbb8MudXdbdsHNswEl5ewAOdRXAuoaJuQ2TOnhz+cO0
oaq9X3YaWjl8SwdvZrLIHLG3jEAQYEXDR+dEtT6vs7HmReMrYhGGoCEVrjSZAaeO
9bQoSe0m8xbuSV19e5A+LZv3bcPcVOe91x/wnjVb98KRQcSQXCBE3yT5Oav/OLKZ
TrdRkKe1yyPMHJiicn3pffdXuG6mIJC8HgYhvJEX/wkWf1BiyRd44dD1cgI1Ttt1
pQoapJCmjA0XHtr15o4TW6hmnrmOOYTOVCCug8h1bWscBedMgdR5Qm+umbUiuC3N
N6mxD49Sc40NCeDboX87rDiObhOXAAFsiE9o39z/tPToVa3TsJsA
bGhvc3QxIDAeBgNVBAoMF0NsaWVudCBDZXJ0aWZpY2F0ZSBEZW1vMB4XDTI0MDcx
OTEyNDczN1oXDTI1MDcxOTEyNDczN1owEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCac3+4rNmH4/N1s4HqR2X168tgS/aA
6sHW5at8mWRnq54Nm11RvnK55jHQYVAdBgJy5M07w0wakp8inxzlY95wqxBimYG6
3Un/1p7mX9FkB4LNISCc6j/s/Ufv85MXPbn0S5rm9UcQO9cINJb1RP1YgDDLN5cx
Mz6X4nyofN8H6Lhvh4JDdBw4DfDEFERkVfF+bkZ7YW4XHEChgzm3RxCF0eeGzIXG
rkkK9AsSdJAhOvTlHPFCQKXTYZhsL5+3Ma4RnWnDWvLTHx6KzoU+twTM2mYhhQuQ
gQpnmDHxGge8kGeHGtfdgAjtVJTE57xF/shP0JU+tuIV8NNhQ/vEmhL0Wa093/Ev
pTVp0EUEuDh9ORRH5K5M4bKJyU4XX5noiht6yOn00uaoJcWduUAWsU+cDSvDTMw8
1opWWm0QIAV3G2yuRSkumHAKqvQLeyeyiKz+OEhyEiZ7EZNExPD0TSpApSTU6aCT
UAvPYGQ59VjsMHTuJ9r4wKIYaDvfL+t72vg2vTQma5cTOBJfIdxH9blFTjEnToH3
LX8t0XndQ2RkiRnIze2p2jUShxo/lWCjCw+2Iaw0A0fNUK1BbOrFRPq1u7AnEuMJ
t7HF50MloItM97R9vofDwgDIzlX/PzlVRcn1WCo8Fr/0EXxPPreX0YDIp1ANQ8fS
v7bKb2vQIxWuCQIDAQABo0IwQDAdBgNVHQ4EFgQUVJVRJJ2k/Z4r0M1AXe6agyD4
uCwwHwYDVR0jBBgwFoAUEHtrxWCk96Ehr60E0HBuwLk2i+IwDQYJKoZIhvcNAQEL
BQADggIBAGEvSkxhxRKmlvKG8wCXop2OaUUAOG16+T96vd+aFYaJNlfGoPvqv4Lw
qaHztVktnRrJ//fpNWOsdxkE1uPU4uyGjl2KbyH81JvkE6A3OX0P4B01n8lcimY2
j3oje6KjORUouYVsypD1VcwfWJgsE3U2Txv5srD8BoemVWgWbWjfyim4kk8C5zlf
tWEazVAaI4MWecqtU4P5gIEomCI7MG9ebxYp5oQhRxeOndOYdUbSzAkZj50gXFA1
+TNkvuhTFlJF0F7qIFVJSJTmJ+6E5B4ddbkyUYwbOdO+P8mz5N5mSljE+EiIQTxo
AwbG8cSivMy/jI3h048tCUONAJzcSWCF4k1r9Qr6xbyW2ud2GmKiFCEYJkYTsMWV
fM/RujTHlGvJ2+bQK5HiNyW0tO9znW9kaoxolu1YBvTh2492v3agK7nALyGGgdo1
/nN/ikgkQiyaCpZwFeooJv1YFU5aDhR9RjIIJ9UbJ8FdAv8Xd00E3viunLTvqqXK
RVMokw+tFQTEzjKofKWYArPDjB9LUbN+vQbumKalis3+NlJ3WolYPrCg55tqt1o3
zXi+xv7120cJFouilRFwrafNFV6F+pRMkMmiWopMnoVJPVXcoqyJRcsmO62uslhg
BLFgAH4H/14drYrgWIMz0no78RInEz0z507zwLkWk5d9W9pJ/4Rf
-----END CERTIFICATE-----

Some files were not shown because too many files have changed in this diff Show More